Beautiful Widgets

Seamless Desktop Widgets with PyQt6

hudbeard
5 min readJul 30, 2023

--

Display useful information at a glance with this simple application in PyQt6. Whether you are a pro or a beginner, this tutorial will work for you. This article takes you step-by-step through the process. The code can be customized to make it your own. Let’s dive right in!

Introduction:

PyQt6 is a very powerful tool for making python GUIs. Before we get started, we need to install PyQt6. That can be done using pip:

pip install PyQt6

Pycaw is needed to make the volume widget:

pip install pycaw

And last, but certainly not least, pyautogui for getting the screen size:

pip install pyautogui

Let’s get started!

We’ll start by creating a layout that we’ll build on later:

# main.py
from PyQt6.QtWidgets import QApplication, QWidget
import sys


class Layout(QWidget):
def __init__(self):
super().__init__()
self.show()


if __name__ == "__main__":
app = QApplication(sys.argv)
widgets = Layout()
sys.exit(app.exec())
That’s Boring

Right now you should see a popup window. That’s cool and all, but not what we are going for. This is where the fun begins. If we add 3 lines of code then it will look a lot better:

# main.py
from PyQt6.QtWidgets import QApplication, QWidget
from PyQt6.QtCore import Qt
import sys


class Layout(QWidget):
def __init__(self):
super().__init__()
self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowStaysOnBottomHint)
self.setGeometry(30, 30, 100, 100)

self.show()


if __name__ == "__main__":
app = QApplication(sys.argv)
widgets = Layout()
sys.exit(app.exec())

A little white box should appear in the top corner of your screen (underneath all other windows). Notice that nothing appeared in the taskbar, and you cannot move the window or bring it in front of any other windows. It is essentially part of your desktop — all thanks to this line:

self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowStaysOnBottomHint)

The setGeometry line determines the location on the screen (x: 30, y: 30) and the height (100) and width (100).

We’re going to do a little magic:

# main.py
from PyQt6.QtWidgets import QApplication, QWidget
from PyQt6.QtCore import Qt
import sys


class Layout(QWidget):
def __init__(self):
super().__init__()
self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowStaysOnBottomHint)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
self.setGeometry(30, 30, 100, 100)

self.show()


if __name__ == "__main__":
app = QApplication(sys.argv)
widgets = Layout()
sys.exit(app.exec())

If you run that code nothing will happen. That’s because the window is transparent. We’re going to start building a widget that controls the volume of your computer. Make a new file called get_volume.py

# get_volume.py
from comtypes import CLSCTX_ALL
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume


class GetVolume(object):
def __init__(self):
devices = AudioUtilities.GetSpeakers()
interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
self.volume = interface.QueryInterface(IAudioEndpointVolume)
self.audio_range = self.volume.GetVolumeRange()
self.current_volume = self.volume.GetMasterVolumeLevel()

def set_volume(self, volume):
self.volume.SetMasterVolumeLevel(volume, None)
self.current_volume = self.volume.GetMasterVolumeLevel()

Perfect, now we have the framework. Let’s create a slider control for it:

# main.py
import sys
from PyQt6.QtWidgets import QApplication, QLabel, QWidget, QVBoxLayout, QSlider, QStackedLayout, QFrame
from PyQt6.QtGui import QFont
from PyQt6.QtCore import Qt
from get_volume import GetVolume


class Layout(QWidget):
def __init__(self):
super().__init__()

self.label = None
self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowStaysOnBottomHint)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)

self.setGeometry(0, 0, 430, 110) # Size and location of window
self.general_layout = QVBoxLayout() # Set a vertical layout
self.general_layout.setSpacing(15)
self.general_layout.setContentsMargins(15, 15, 15, 15)

self.setLayout(self.general_layout)
self.volume = GetVolume() # The volume file we made earlier
self.make_widget()
self.general_layout.addStretch() # Fill any extra space in the window.

self.show()

def make_widget(self):
self.label = QLabel(self) # All widgets are Labels
self.label.setFixedSize(430, 110) # Set the size of Label
self.label.setFont(QFont("Segoe UI", 12, QFont.Weight.Bold))
self.label.setStyleSheet('QWidget{background-color: '
'rgba(255,255,255, .4);'
' padding: 30px; border-radius: 30px; backdrop-filter: blur(5px);}')
# Do some styling to said widget
self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.volume_widget()

def volume_widget(self):
# Sliders are a little finicky so we do some extra formatting
s_layout = QStackedLayout()
s_layout.setSpacing(0)
s_layout.setContentsMargins(0, 0, 0, 0)
s_layout.setStackingMode(QStackedLayout.StackingMode.StackAll)

slider = QSlider(self)
centered_frame = QFrame()
centered_frame.setLayout(QVBoxLayout())
centered_frame.layout().setAlignment(Qt.AlignmentFlag.AlignCenter)

centered_frame.layout().addWidget(slider)

slider.setOrientation(Qt.Orientation.Horizontal)
slider.setMinimum(int(self.volume.audio_range[0] * 100))
slider.setMaximum(int(self.volume.audio_range[1] * 100))
slider.setTickPosition(QSlider.TickPosition.TicksBelow)
slider.setTickInterval(100)
slider.setValue(int(self.volume.current_volume * 100))

slider.setFixedSize(250, 40)
slider.valueChanged.connect(self.update_volume_on_slider_change) # Connect the slider to our volume file

centered_frame.layout().addWidget(slider)

self.label.setText(f"""
<html>
<p style="font-size: 30px; white-space: pre;">🔇{"&#9;" * 4}🔊</p>
</html>
""")

s_layout.addWidget(self.label)
s_layout.addWidget(centered_frame)
s_layout.setCurrentIndex(1)

self.general_layout.addLayout(s_layout) # add it to the layout

def update_volume_on_slider_change(self, value):
self.volume.set_volume(value / 100)


if __name__ == '__main__':
app = QApplication(sys.argv)
widgets = Layout()
sys.exit(app.exec())

A widget with a slider control should appear in the top corner (underneath all windows):

A Working Slider!

Voila!

You have the freedom to build your own widgets with this technique. I made a way to dynamically build a layout on GitHub. If you would like to dive deeper and build more, then you should check out my GitHub and star the repository.

Full Code:

Follow me on GitHub:

Ways to support hudbeard’s writing:

  • 👏 Clap 20x if you enjoyed this article!
  • 💡 You can clap 20x in one long press!
  • 🗨️ Comment your thoughts. Tell me if you have similar stories!
  • 👀 Follow me to see more content like this!
  • 📧 Subscribe to get my articles as emails!

Thanks for reading! Happy Coding!

More like this by hudbeard:

--

--

hudbeard

Professional software engineer since age 14. Programming since age 6. Python is my love language. 🐍