Files
glass/app.py
2026-06-30 01:22:58 +00:00

503 lines
12 KiB
Python

# latest
import ctypes
import logging
import signal
import sys
from PySide6.QtCore import QPoint, Qt
from PySide6.QtGui import QColor
from PySide6.QtWidgets import (
QApplication,
QCheckBox,
QColorDialog,
QLabel,
QPushButton,
QSlider,
QVBoxLayout,
QWidget,
)
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
try:
ctypes.windll.user32.SetProcessDPIAware()
except Exception:
pass
DWMWA_USE_IMMERSIVE_DARK_MODE = 20
DWMWA_WINDOW_CORNER_PREFERENCE = 33
DWMWCP_ROUND = 2
class WindowConfig:
width = 800
height = 500
opacity = 0.99
radius = 11
resize_margin = 8
minimum_width = 300
minimum_height = 200
background_alpha = 190
border_alpha = 190
class GlassWindow(QWidget):
def __init__(self):
super().__init__(
None,
Qt.FramelessWindowHint | Qt.Window | Qt.WindowStaysOnTopHint,
)
self.cfg = WindowConfig()
self.background_color = QColor(155, 155, 155)
self._drag_offset = None
self._resize_edges = None
self._resize_start_pos = QPoint()
self._resize_start_geo = self.geometry()
self.setAttribute(Qt.WA_TranslucentBackground, True)
self.setWindowOpacity(self.cfg.opacity)
self.resize(self.cfg.width, self.cfg.height)
self.setMinimumSize(
self.cfg.minimum_width,
self.cfg.minimum_height,
)
self.setMouseTracking(True)
self.panel = QWidget(self)
self.panel.setObjectName("panel")
layout = QVBoxLayout(self.panel)
layout.setContentsMargins(24, 24, 24, 24)
layout.setSpacing(16)
title = QLabel("")
title.setStyleSheet(
"background:transparent;color:white;font-size:24px;font-weight:600;"
)
body = QLabel("opacity")
body.setStyleSheet("background:transparent;color:transparent;")
layout.addWidget(title)
layout.addWidget(body)
layout.addStretch()
self.slider = QSlider(
Qt.Horizontal,
self.panel,
)
self.slider.setRange(0, 255)
self.slider.setFixedWidth(255)
self.slider.setValue(
self.cfg.background_alpha,
)
self.slider.valueChanged.connect(
self.update_panel_style,
)
self.color_button = QPushButton(
self.panel,
)
self.color_button.setFixedSize(22, 22)
self.color_button.clicked.connect(
self.pick_background_color,
)
self.pin_button = QPushButton(
"🗗",
self.panel,
)
self.pin_button.setCheckable(True)
self.pin_button.setChecked(True)
self.pin_button.setFixedSize(28, 28)
self.pin_button.setCursor(Qt.ArrowCursor)
self.pin_button.setToolTip("Always on Top")
self.pin_button.toggled.connect(
self.toggle_always_on_top,
)
self.setCursor(Qt.ArrowCursor)
self.panel.setCursor(Qt.ArrowCursor)
self.slider.setCursor(Qt.ArrowCursor)
self.color_button.setCursor(Qt.ArrowCursor)
self.pin_button.setCursor(Qt.ArrowCursor)
self.pin_button.setObjectName("pinButton")
self.update_panel_style()
def toggle_always_on_top(
self,
enabled: bool,
):
geometry = self.geometry()
self.setWindowFlag(
Qt.WindowStaysOnTopHint,
enabled,
)
self.show()
self.setGeometry(geometry)
def pick_background_color(self):
dialog = QColorDialog(
self.background_color,
self,
)
dialog.setOption(
QColorDialog.DontUseNativeDialog,
True,
)
dialog.setWindowTitle(
"background",
)
dialog.setStyleSheet("""
QColorDialog{
background:#030303;
color:#f2f2f2;
}
QWidget{
background:#090909;
color:#f2f2f2;
}
QLabel{
background:transparent;
color:#f2f2f2;
}
QGroupBox{
color:#f2f2f2;
border:1px solid #3a3a3a;
margin-top:8px;
}
QGroupBox::title{
left:8px;
padding:0 4px;
}
QPushButton{
background:#2a2a2a;
color:white;
border:1px solid #444;
border-radius:4px;
padding:5px 12px;
}
QPushButton:hover{
background:#343434;
}
QPushButton:pressed{
background:#404040;
}
QLineEdit,
QSpinBox,
QComboBox{
background:#2a2a2a;
color:white;
border:1px solid #444;
selection-background-color:#5DFFCE;
}
QTabBar::tab{
background:#2a2a2a;
color:white;
padding:6px 10px;
}
QTabBar::tab:selected{
background:#404040;
}
""")
if dialog.exec():
self.background_color = dialog.currentColor()
self.update_panel_style()
# v4
def update_panel_style(self, value=None):
if value is not None:
self.cfg.background_alpha = value
r = self.background_color.red()
g = self.background_color.green()
b = self.background_color.blue()
luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255.0
groove = f"rgba({r},{g},{b},{max(28, self.cfg.background_alpha - 135)})"
fill = f"rgba({r},{g},{b},{min(255, self.cfg.background_alpha + 45)})"
if luminance > 0.60:
handle = "#1b1b1d"
handle_hover = "#121214"
handle_pressed = "#09090b"
handle_border = "rgba(255,255,255,32)"
else:
handle = "#f7f7f8"
handle_hover = "#ffffff"
handle_pressed = "#ececee"
handle_border = "rgba(0,0,0,28)"
self.setStyleSheet(f"""
#panel {{
background: rgba({r},{g},{b},{self.cfg.background_alpha});
border: 1px solid rgba(255,255,255,.2);
border-radius: {self.cfg.radius}px;
}}
QPushButton {{
margin: 2px;
background: rgb({r},{g},{b});
border: 1px solid rgba(255,255,255,24);
border-radius: 4px;
}}
QPushButton:hover {{
border: 1px solid rgba(255,255,255,48);
}}
QSlider {{
background: transparent;
}}
QSlider::groove:horizontal {{
height: 4px;
background: {groove};
border: none;
border-radius: 2px;
}}
QSlider::sub-page:horizontal {{
background: {fill};
border-radius: 2px;
}}
QSlider::add-page:horizontal {{
background: {groove};
border-radius: 2px;
}}
QSlider::handle:horizontal {{
width: 14px;
height: 14px;
margin: -5px 0;
background: {handle};
border: 1px solid {handle_border};
border-radius: 7px;
}}
QSlider::handle:horizontal:hover {{
background: {handle_hover};
}}
QSlider::handle:horizontal:pressed {{
background: {handle_pressed};
}}
QPushButton {{
margin: 2px;
background: rgb({r},{g},{b});
border: 1px solid rgba(255,255,255,24);
border-radius: 4px;
}}
QPushButton#pinButton {{
color: rgba(255,255,255,180);
}}
QPushButton#pinButton:checked {{
background: #064C2C1C;
border: 1px solid rgba(255,255,255,40);
color: white;
font-weight: 600;
}}
""")
def resizeEvent(self, event):
self.panel.setGeometry(self.rect())
margin = 24
slider_y = self.panel.height() - self.slider.height() - margin
spacing_slider_color = 10
spacing_color_pin = 8
self.slider.move(
self.panel.width() - self.slider.width() - 67 - margin,
slider_y,
)
self.color_button.move(
self.slider.x() + self.slider.width() + spacing_slider_color,
slider_y + (self.slider.height() - self.color_button.height()) // 2,
)
self.pin_button.move(
self.color_button.x() + self.color_button.width() + spacing_color_pin,
slider_y + (self.slider.height() - self.pin_button.height()) // 2,
)
super().resizeEvent(event)
def showEvent(self, event):
super().showEvent(event)
hwnd = int(self.winId())
try:
dark = ctypes.c_int(1)
ctypes.windll.dwmapi.DwmSetWindowAttribute(
hwnd,
DWMWA_USE_IMMERSIVE_DARK_MODE,
ctypes.byref(dark),
ctypes.sizeof(dark),
)
corner = ctypes.c_int(DWMWCP_ROUND)
ctypes.windll.dwmapi.DwmSetWindowAttribute(
hwnd,
DWMWA_WINDOW_CORNER_PREFERENCE,
ctypes.byref(corner),
ctypes.sizeof(corner),
)
except Exception as ex:
logging.warning("DWM setup failed: %s", ex)
# v2
def _hit_test(self, pos):
m = self.cfg.resize_margin + 6
x = pos.x()
y = pos.y()
w = self.width()
h = self.height()
left = x <= m
right = x >= w - m
top = y <= m
bottom = y >= h - m
return left, top, right, bottom
def mousePressEvent(self, e):
if e.button() != Qt.LeftButton:
return super().mousePressEvent(e)
edges = self._hit_test(e.position().toPoint())
if any(edges):
self._resize_edges = edges
self._resize_start_pos = e.globalPosition().toPoint()
self._resize_start_geo = self.geometry()
return
self._drag_offset = (
e.globalPosition().toPoint() - self.frameGeometry().topLeft()
)
def mouseMoveEvent(self, e):
pos = e.position().toPoint()
if self._resize_edges is not None:
dx = e.globalPosition().toPoint().x() - self._resize_start_pos.x()
dy = e.globalPosition().toPoint().y() - self._resize_start_pos.y()
geo = self._resize_start_geo
x = geo.x()
y = geo.y()
w = geo.width()
h = geo.height()
left, top, right, bottom = self._resize_edges
if left:
x += dx
w -= dx
if right:
w += dx
if top:
y += dy
h -= dy
if bottom:
h += dy
if w < self.minimumWidth():
if left:
x -= self.minimumWidth() - w
w = self.minimumWidth()
if h < self.minimumHeight():
if top:
y -= self.minimumHeight() - h
h = self.minimumHeight()
self.setGeometry(x, y, w, h)
return
if self._drag_offset is not None:
self.move(e.globalPosition().toPoint() - self._drag_offset)
return
left, top, right, bottom = self._hit_test(pos)
if (left and top) or (right and bottom):
self.setCursor(Qt.SizeFDiagCursor)
elif (right and top) or (left and bottom):
self.setCursor(Qt.SizeBDiagCursor)
elif left or right:
self.setCursor(Qt.SizeHorCursor)
elif top or bottom:
self.setCursor(Qt.SizeVerCursor)
else:
self.setCursor(Qt.ArrowCursor)
# v1
def mouseReleaseEvent(self, e):
self._drag_offset = None
self._resize_edges = None
super().mouseReleaseEvent(e)
def keyPressEvent(self, e):
if e.key() == Qt.Key_Escape:
self.close()
else:
super().keyPressEvent(e)
def main():
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QApplication(sys.argv)
w = GlassWindow()
w.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()