[SCRIPT] Updated
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
from .bar import Bar
|
||||
from .control_center import ControlCenter
|
||||
from .launcher import Launcher
|
||||
from .notification_popup import NotificationPopup
|
||||
from .osd import OSD
|
||||
from .powermenu import Powermenu
|
||||
from .settings import Settings
|
||||
|
||||
__all__ = [
|
||||
"Bar",
|
||||
"ControlCenter",
|
||||
"Launcher",
|
||||
"NotificationPopup",
|
||||
"OSD",
|
||||
"Powermenu",
|
||||
"Settings",
|
||||
]
|
||||
|
||||
Binary file not shown.
@@ -1,25 +1,3 @@
|
||||
from ignis.widgets import Widget
|
||||
from .workspaces import workspaces
|
||||
from .kb_layout import kb_layout
|
||||
from .clock import clock
|
||||
from .pinned_apps import pinned_apps
|
||||
from .tray import tray
|
||||
from .battery import battery_widget
|
||||
# from .wallpaper_engine import wallpaper_engine_widget
|
||||
from .bar import Bar
|
||||
|
||||
def bar(monitor: int) -> Widget.Window:
|
||||
return Widget.Window(
|
||||
anchor=["left", "top", "right"],
|
||||
exclusivity="exclusive",
|
||||
monitor=monitor,
|
||||
namespace=f"ignis_BAR_{monitor}",
|
||||
layer="top",
|
||||
kb_mode="none",
|
||||
child=Widget.CenterBox(
|
||||
css_classes=["bar-widget"],
|
||||
start_widget=Widget.Box(child=[workspaces()]),
|
||||
center_widget=Widget.Box(child=[pinned_apps()]),
|
||||
end_widget=Widget.Box(child=[tray(), kb_layout(), battery_widget(), clock(monitor)]),
|
||||
), # wallpaper_engine_widget(),
|
||||
css_classes=["unset"],
|
||||
)
|
||||
__all__ = ["Bar"]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,25 @@
|
||||
from ignis.widgets import Widget
|
||||
from .widgets import StatusPill, Tray, KeyboardLayout, Battery, Apps, Workspaces
|
||||
|
||||
|
||||
class Bar(Widget.Window):
|
||||
__gtype_name__ = "Bar"
|
||||
|
||||
def __init__(self, monitor: int):
|
||||
super().__init__(
|
||||
anchor=["left", "top", "right"],
|
||||
exclusivity="exclusive",
|
||||
monitor=monitor,
|
||||
namespace=f"ignis_BAR_{monitor}",
|
||||
layer="top",
|
||||
kb_mode="none",
|
||||
child=Widget.CenterBox(
|
||||
css_classes=["bar-widget"],
|
||||
start_widget=Widget.Box(child=[Workspaces()]),
|
||||
center_widget=Widget.Box(child=[Apps()]),
|
||||
end_widget=Widget.Box(
|
||||
child=[Tray(), KeyboardLayout(), Battery(), StatusPill(monitor)]
|
||||
),
|
||||
),
|
||||
css_classes=["unset"],
|
||||
)
|
||||
@@ -0,0 +1,31 @@
|
||||
from ignis.widgets import Widget
|
||||
from ignis.services.network import Ethernet, Wifi
|
||||
|
||||
|
||||
class IndicatorIcon(Widget.Icon):
|
||||
def __init__(self, css_classes: list[str] = [], **kwargs):
|
||||
super().__init__(
|
||||
style="margin-right: 0.5rem;", css_classes=["unset"] + css_classes, **kwargs
|
||||
)
|
||||
|
||||
|
||||
class NetworkIndicatorIcon(IndicatorIcon):
|
||||
def __init__(
|
||||
self, device_type: Ethernet | Wifi, other_device_type: Wifi | Ethernet
|
||||
):
|
||||
self._device_type = device_type
|
||||
self._other_device_type = other_device_type
|
||||
|
||||
super().__init__(icon_name=device_type.bind("icon-name"))
|
||||
|
||||
for binding in (
|
||||
device_type.bind("devices", self.__check_visibility),
|
||||
other_device_type.bind("is_connected", self.__check_visibility),
|
||||
device_type.bind("is_connected", self.__check_visibility),
|
||||
):
|
||||
self.visible = binding # type: ignore
|
||||
|
||||
def __check_visibility(self, *args) -> bool:
|
||||
return len(self._device_type.devices) > 0 and (
|
||||
not self._other_device_type.is_connected or self._device_type.is_connected
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
from .pill import StatusPill
|
||||
from .tray import Tray
|
||||
from .kb_layout import KeyboardLayout
|
||||
from .battery import Battery
|
||||
from .apps import Apps
|
||||
from .workspaces import Workspaces
|
||||
|
||||
__all__ = ["StatusPill", "Tray", "KeyboardLayout", "Battery", "Apps", "Workspaces"]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,52 @@
|
||||
from ignis.widgets import Widget
|
||||
from ignis.app import IgnisApp
|
||||
from ignis.services.applications import ApplicationsService, Application
|
||||
|
||||
applications = ApplicationsService.get_default()
|
||||
app = IgnisApp.get_default()
|
||||
|
||||
TERMINAL_FORMAT = "kitty %command%"
|
||||
|
||||
|
||||
class AppItem(Widget.Button):
|
||||
def __init__(self, app: Application):
|
||||
menu = Widget.PopoverMenu(
|
||||
items=[
|
||||
Widget.MenuItem(label="Launch", on_activate=lambda x: app.launch()),
|
||||
Widget.Separator(),
|
||||
]
|
||||
+ [
|
||||
Widget.MenuItem(
|
||||
label=i.name, on_activate=lambda x, action=i: action.launch()
|
||||
)
|
||||
for i in app.actions
|
||||
]
|
||||
+ [
|
||||
Widget.Separator(),
|
||||
Widget.MenuItem(label="Unpin", on_activate=lambda x: app.unpin()),
|
||||
]
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
child=Widget.Box(child=[Widget.Icon(image=app.icon, pixel_size=32), menu]),
|
||||
on_click=lambda x: app.launch(terminal_format=TERMINAL_FORMAT),
|
||||
on_right_click=lambda x: menu.popup(),
|
||||
css_classes=["pinned-app", "unset"],
|
||||
)
|
||||
|
||||
|
||||
class Apps(Widget.Box):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
child=applications.bind(
|
||||
"pinned",
|
||||
transform=lambda value: [AppItem(app) for app in value]
|
||||
+ [
|
||||
Widget.Button(
|
||||
child=Widget.Icon(image="start-here-symbolic", pixel_size=32),
|
||||
on_click=lambda x: app.toggle_window("ignis_LAUNCHER"),
|
||||
css_classes=["pinned-app", "unset"],
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
from ignis.widgets import Widget
|
||||
from ignis.services.upower import UPowerService, UPowerDevice
|
||||
|
||||
upower = UPowerService.get_default()
|
||||
|
||||
|
||||
class BatteryItem(Widget.Box):
|
||||
def __init__(self, device: UPowerDevice):
|
||||
super().__init__(
|
||||
css_classes=["battery-item"],
|
||||
setup=lambda self: device.connect("removed", lambda x: self.unparent()),
|
||||
child=[
|
||||
Widget.Icon(
|
||||
icon_name=device.bind("icon_name"), css_classes=["battery-icon"]
|
||||
),
|
||||
Widget.Label(
|
||||
label=device.bind("percent", lambda x: f"{int(x)}%"),
|
||||
css_classes=["battery-percent"],
|
||||
),
|
||||
Widget.Scale(
|
||||
min=0,
|
||||
max=100,
|
||||
value=device.bind("percent"),
|
||||
sensitive=False,
|
||||
css_classes=["battery-scale"],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class Battery(Widget.Box):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
setup=lambda self: upower.connect(
|
||||
"battery-added", lambda x, device: self.append(BatteryItem(device))
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,18 @@
|
||||
from ignis.widgets import Widget
|
||||
from ignis.services.hyprland import HyprlandService
|
||||
|
||||
hyprland = HyprlandService.get_default()
|
||||
|
||||
|
||||
class KeyboardLayout(Widget.Button):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
css_classes=["kb-layout", "unset"],
|
||||
on_click=lambda x: hyprland.main_keyboard.switch_layout("1"),
|
||||
visible=hyprland.is_available,
|
||||
child=Widget.Label(
|
||||
label=hyprland.main_keyboard.bind(
|
||||
"active_keymap", transform=lambda value: value[:2].lower()
|
||||
)
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,111 @@
|
||||
import datetime
|
||||
from ignis.widgets import Widget
|
||||
from ignis.app import IgnisApp
|
||||
from ignis.utils import Utils
|
||||
from ignis.variable import Variable
|
||||
from ignis.services.network import NetworkService
|
||||
from ignis.services.notifications import NotificationService
|
||||
from ignis.services.recorder import RecorderService
|
||||
from ignis.services.audio import AudioService
|
||||
from ..indicator_icon import IndicatorIcon, NetworkIndicatorIcon
|
||||
from ignis.options import options
|
||||
|
||||
network = NetworkService.get_default()
|
||||
notifications = NotificationService.get_default()
|
||||
recorder = RecorderService.get_default()
|
||||
audio = AudioService.get_default()
|
||||
|
||||
app = IgnisApp.get_default()
|
||||
|
||||
current_time = Variable(
|
||||
value=Utils.Poll(1000, lambda x: datetime.datetime.now().strftime("%H:%M:%S")).bind(
|
||||
"output"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class WifiIcon(NetworkIndicatorIcon):
|
||||
def __init__(self):
|
||||
super().__init__(device_type=network.wifi, other_device_type=network.ethernet)
|
||||
|
||||
|
||||
class EthernetIcon(NetworkIndicatorIcon):
|
||||
def __init__(self):
|
||||
super().__init__(device_type=network.ethernet, other_device_type=network.wifi)
|
||||
|
||||
|
||||
class VpnIcon(IndicatorIcon):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
image=network.vpn.bind("icon_name"),
|
||||
visible=network.vpn.bind("is_connected"),
|
||||
)
|
||||
|
||||
|
||||
class DNDIcon(IndicatorIcon):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
image="notification-disabled-symbolic",
|
||||
visible=options.notifications.bind("dnd"),
|
||||
)
|
||||
|
||||
|
||||
class RecorderIcon(IndicatorIcon):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
image="media-record-symbolic",
|
||||
css_classes=["record-indicator"],
|
||||
setup=lambda self: recorder.connect(
|
||||
"notify::is-paused", self.__update_css_class
|
||||
),
|
||||
visible=recorder.bind("active"),
|
||||
)
|
||||
|
||||
def __update_css_class(self, *args) -> None:
|
||||
if recorder.is_paused:
|
||||
self.remove_css_class("active")
|
||||
else:
|
||||
self.add_css_class("active")
|
||||
|
||||
|
||||
class VolumeIcon(IndicatorIcon):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
image=audio.speaker.bind("icon_name"),
|
||||
)
|
||||
|
||||
|
||||
class StatusPill(Widget.Button):
|
||||
def __init__(self, monitor: int):
|
||||
self._monitor = monitor
|
||||
self._window: Widget.Window = app.get_window("ignis_CONTROL_CENTER") # type: ignore
|
||||
|
||||
super().__init__(
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
RecorderIcon(),
|
||||
WifiIcon(),
|
||||
EthernetIcon(),
|
||||
VpnIcon(),
|
||||
VolumeIcon(),
|
||||
DNDIcon(),
|
||||
Widget.Label(
|
||||
label=current_time.bind("value"),
|
||||
),
|
||||
]
|
||||
),
|
||||
css_classes=self._window.bind(
|
||||
"visible",
|
||||
lambda value: ["clock", "unset", "active"]
|
||||
if value
|
||||
else ["clock", "unset"],
|
||||
),
|
||||
on_click=self.__on_click,
|
||||
)
|
||||
|
||||
def __on_click(self, x) -> None:
|
||||
if self._window.monitor == self._monitor:
|
||||
self._window.visible = not self._window.visible
|
||||
else:
|
||||
self._window.set_monitor(self._monitor)
|
||||
self._window.visible = True
|
||||
@@ -0,0 +1,42 @@
|
||||
import asyncio
|
||||
from ignis.widgets import Widget
|
||||
from ignis.services.system_tray import SystemTrayService, SystemTrayItem
|
||||
|
||||
system_tray = SystemTrayService.get_default()
|
||||
|
||||
|
||||
class TrayItem(Widget.Button):
|
||||
__gtype_name__ = "TrayItem"
|
||||
|
||||
def __init__(self, item: SystemTrayItem):
|
||||
if item.menu:
|
||||
menu = item.menu.copy()
|
||||
else:
|
||||
menu = None
|
||||
|
||||
super().__init__(
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
Widget.Icon(image=item.bind("icon"), pixel_size=24),
|
||||
menu,
|
||||
]
|
||||
),
|
||||
tooltip_text=item.bind("tooltip"),
|
||||
on_click=lambda x: asyncio.create_task(item.activate_async()),
|
||||
setup=lambda self: item.connect("removed", lambda x: self.unparent()),
|
||||
on_right_click=lambda x: menu.popup() if menu else None,
|
||||
css_classes=["tray-item", "unset"],
|
||||
)
|
||||
|
||||
|
||||
class Tray(Widget.Box):
|
||||
__gtype_name__ = "Tray"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
css_classes=["tray"],
|
||||
setup=lambda self: system_tray.connect(
|
||||
"added", lambda x, item: self.append(TrayItem(item))
|
||||
),
|
||||
spacing=10,
|
||||
)
|
||||
@@ -0,0 +1,49 @@
|
||||
from ignis.widgets import Widget
|
||||
from ignis.services.hyprland import HyprlandService, HyprlandWorkspace
|
||||
|
||||
hyprland = HyprlandService.get_default()
|
||||
|
||||
|
||||
class WorkspaceButton(Widget.Button):
|
||||
def __init__(self, workspace: HyprlandWorkspace) -> None:
|
||||
super().__init__(
|
||||
css_classes=["workspace", "unset"],
|
||||
on_click=lambda x: workspace.switch_to(),
|
||||
halign="start",
|
||||
valign="center",
|
||||
)
|
||||
if workspace.id == hyprland.active_workspace.id:
|
||||
self.add_css_class("active")
|
||||
|
||||
|
||||
def scroll_workspaces(direction: str) -> None:
|
||||
current = hyprland.active_workspace.id
|
||||
if direction == "up":
|
||||
target = current - 1
|
||||
hyprland.switch_to_workspace(target)
|
||||
else:
|
||||
target = current + 1
|
||||
if target == 11:
|
||||
return
|
||||
hyprland.switch_to_workspace(target)
|
||||
|
||||
|
||||
class Workspaces(Widget.Box):
|
||||
def __init__(self):
|
||||
if hyprland.is_available:
|
||||
child = [
|
||||
Widget.EventBox(
|
||||
on_scroll_up=lambda x: scroll_workspaces("up"),
|
||||
on_scroll_down=lambda x: scroll_workspaces("down"),
|
||||
css_classes=["workspaces"],
|
||||
child=hyprland.bind_many(
|
||||
["workspaces", "active_workspace"],
|
||||
transform=lambda workspaces, *_: [
|
||||
WorkspaceButton(i) for i in workspaces
|
||||
],
|
||||
),
|
||||
)
|
||||
]
|
||||
else:
|
||||
child = []
|
||||
super().__init__(child=child)
|
||||
@@ -1,62 +1,3 @@
|
||||
from ignis.widgets import Widget
|
||||
from ignis.app import IgnisApp
|
||||
from .volume import volume_control
|
||||
from .quick_settings import quick_settings
|
||||
from .user import user
|
||||
from .media import media
|
||||
from .notification_center import notification_center
|
||||
from .brightness import brightness_slider
|
||||
from .control_center import ControlCenter
|
||||
|
||||
app = IgnisApp.get_default()
|
||||
|
||||
|
||||
def control_center_widget() -> Widget.Box:
|
||||
return Widget.Box(
|
||||
vertical=True,
|
||||
css_classes=["control-center"],
|
||||
child=[
|
||||
Widget.Box(
|
||||
vertical=True,
|
||||
css_classes=["control-center-widget"],
|
||||
child=[
|
||||
quick_settings(),
|
||||
volume_control(),
|
||||
brightness_slider(),
|
||||
user(),
|
||||
media(),
|
||||
],
|
||||
),
|
||||
notification_center(),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def control_center() -> Widget.RevealerWindow:
|
||||
revealer = Widget.Revealer(
|
||||
transition_type="slide_left",
|
||||
child=control_center_widget(),
|
||||
transition_duration=300,
|
||||
reveal_child=True,
|
||||
)
|
||||
box = Widget.Box(
|
||||
child=[
|
||||
Widget.Button(
|
||||
vexpand=True,
|
||||
hexpand=True,
|
||||
css_classes=["unset"],
|
||||
on_click=lambda x: app.close_window("ignis_CONTROL_CENTER"),
|
||||
),
|
||||
revealer,
|
||||
],
|
||||
)
|
||||
return Widget.RevealerWindow(
|
||||
visible=False,
|
||||
popup=True,
|
||||
kb_mode="on_demand",
|
||||
layer="top",
|
||||
css_classes=["unset"],
|
||||
anchor=["top", "right", "bottom", "left"],
|
||||
namespace="ignis_CONTROL_CENTER",
|
||||
child=box,
|
||||
revealer=revealer,
|
||||
)
|
||||
__all__ = ["ControlCenter"]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,66 @@
|
||||
from ignis.widgets import Widget
|
||||
from ignis.app import IgnisApp
|
||||
from .widgets import (
|
||||
QuickSettings,
|
||||
Brightness,
|
||||
VolumeSlider,
|
||||
User,
|
||||
Media,
|
||||
NotificationCenter,
|
||||
)
|
||||
from .menu import opened_menu
|
||||
|
||||
app = IgnisApp.get_default()
|
||||
|
||||
|
||||
class ControlCenter(Widget.RevealerWindow):
|
||||
def __init__(self):
|
||||
revealer = Widget.Revealer(
|
||||
transition_type="slide_left",
|
||||
child=Widget.Box(
|
||||
vertical=True,
|
||||
css_classes=["control-center"],
|
||||
child=[
|
||||
Widget.Box(
|
||||
vertical=True,
|
||||
css_classes=["control-center-widget"],
|
||||
child=[
|
||||
QuickSettings(),
|
||||
VolumeSlider("speaker"),
|
||||
VolumeSlider("microphone"),
|
||||
Brightness(),
|
||||
User(),
|
||||
Media(),
|
||||
],
|
||||
),
|
||||
NotificationCenter(),
|
||||
],
|
||||
),
|
||||
transition_duration=300,
|
||||
reveal_child=True,
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
visible=False,
|
||||
popup=True,
|
||||
kb_mode="on_demand",
|
||||
layer="top",
|
||||
css_classes=["unset"],
|
||||
anchor=["top", "right", "bottom", "left"],
|
||||
namespace="ignis_CONTROL_CENTER",
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
Widget.Button(
|
||||
vexpand=True,
|
||||
hexpand=True,
|
||||
css_classes=["unset"],
|
||||
on_click=lambda x: app.close_window("ignis_CONTROL_CENTER"),
|
||||
),
|
||||
revealer,
|
||||
],
|
||||
),
|
||||
setup=lambda self: self.connect(
|
||||
"notify::visible", lambda x, y: opened_menu.set_value("")
|
||||
),
|
||||
revealer=revealer,
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
from gi.repository import GObject # type: ignore
|
||||
from ignis.widgets import Widget
|
||||
from ignis.variable import Variable
|
||||
from ignis.base_widget import BaseWidget
|
||||
|
||||
opened_menu = Variable()
|
||||
|
||||
|
||||
class Menu(Widget.Revealer):
|
||||
def __init__(self, name: str, child: list[BaseWidget], **kwargs):
|
||||
self._name = name
|
||||
self._box = Widget.Box(
|
||||
vertical=True,
|
||||
css_classes=["control-center-menu"],
|
||||
child=child,
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
transition_type="slide_down",
|
||||
transition_duration=300,
|
||||
reveal_child=opened_menu.bind("value", lambda value: value == self._name),
|
||||
child=self._box,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def toggle(self) -> None:
|
||||
if self.reveal_child:
|
||||
opened_menu.value = ""
|
||||
else:
|
||||
opened_menu.value = self._name
|
||||
|
||||
@GObject.Property
|
||||
def box(self) -> Widget.Box:
|
||||
return self._box
|
||||
@@ -2,6 +2,7 @@ from ignis.widgets import Widget
|
||||
from gi.repository import GObject # type: ignore
|
||||
from typing import Callable
|
||||
from ignis.gobject import Binding
|
||||
from .menu import Menu
|
||||
|
||||
|
||||
class QSButton(Widget.Button):
|
||||
@@ -11,13 +12,13 @@ class QSButton(Widget.Button):
|
||||
icon_name: str | Binding,
|
||||
on_activate: Callable | None = None,
|
||||
on_deactivate: Callable | None = None,
|
||||
content: Widget.Revealer | None = None,
|
||||
menu: Menu | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
self.on_activate = on_activate
|
||||
self.on_deactivate = on_deactivate
|
||||
self._active = False
|
||||
self._content = content
|
||||
self._menu = menu
|
||||
super().__init__(
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
@@ -27,9 +28,9 @@ class QSButton(Widget.Button):
|
||||
halign="end",
|
||||
hexpand=True,
|
||||
pixel_size=20,
|
||||
rotated=content.bind("reveal_child"),
|
||||
rotated=menu.bind("reveal_child"),
|
||||
)
|
||||
if content
|
||||
if menu
|
||||
else None,
|
||||
]
|
||||
),
|
||||
@@ -60,5 +61,5 @@ class QSButton(Widget.Button):
|
||||
self.remove_css_class("active")
|
||||
|
||||
@GObject.Property
|
||||
def content(self) -> Widget.Revealer | None:
|
||||
return self._content
|
||||
def menu(self) -> Menu | None:
|
||||
return self._menu
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
from .quick_settings import QuickSettings
|
||||
from .brightness import Brightness
|
||||
from .volume import VolumeSlider
|
||||
from .user import User
|
||||
from .media import Media
|
||||
from .notification_center import NotificationCenter
|
||||
|
||||
__all__ = [
|
||||
"QuickSettings",
|
||||
"Brightness",
|
||||
"VolumeSlider",
|
||||
"User",
|
||||
"Media",
|
||||
"NotificationCenter",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,29 @@
|
||||
import asyncio
|
||||
from ignis.services.backlight import BacklightService
|
||||
from ignis.widgets import Widget
|
||||
|
||||
backlight = BacklightService.get_default()
|
||||
|
||||
|
||||
class Brightness(Widget.Box):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
visible=backlight.bind("available"),
|
||||
hexpand=True,
|
||||
style="margin-top: 0.25rem;",
|
||||
child=[
|
||||
Widget.Icon(
|
||||
image="display-brightness-symbolic",
|
||||
css_classes=["material-slider-icon"],
|
||||
pixel_size=18,
|
||||
),
|
||||
Widget.Scale(
|
||||
min=0,
|
||||
max=backlight.max_brightness,
|
||||
hexpand=True,
|
||||
value=backlight.bind("brightness"),
|
||||
css_classes=["material-slider"],
|
||||
on_change=lambda x: asyncio.create_task(backlight.set_brightness_async(x.value)),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,211 @@
|
||||
import os
|
||||
import ignis
|
||||
import asyncio
|
||||
from ignis.widgets import Widget
|
||||
from ignis.services.mpris import MprisService, MprisPlayer
|
||||
from ignis.utils import Utils
|
||||
from services.material import MaterialService
|
||||
from ignis.app import IgnisApp
|
||||
from ignis.exceptions import StylePathNotFoundError
|
||||
|
||||
|
||||
mpris = MprisService.get_default()
|
||||
app = IgnisApp.get_default()
|
||||
material = MaterialService.get_default()
|
||||
|
||||
MEDIA_TEMPLATE = Utils.get_current_dir() + "/media.scss"
|
||||
MEDIA_SCSS_CACHE_DIR = ignis.CACHE_DIR + "/media" # type: ignore
|
||||
MEDIA_ART_FALLBACK = Utils.get_current_dir() + "/../../../misc/media-art-fallback.png"
|
||||
os.makedirs(MEDIA_SCSS_CACHE_DIR, exist_ok=True)
|
||||
|
||||
|
||||
PLAYER_ICONS = {
|
||||
"spotify": "spotify-symbolic",
|
||||
"firefox": "firefox-browser-symbolic",
|
||||
"chrome": "chrome-symbolic",
|
||||
None: "folder-music-symbolic",
|
||||
}
|
||||
|
||||
|
||||
class Player(Widget.Revealer):
|
||||
def __init__(self, player: MprisPlayer) -> None:
|
||||
self._player = player
|
||||
self._colors_path = f"{MEDIA_SCSS_CACHE_DIR}/{self._player.desktop_entry}.scss"
|
||||
player.connect("closed", lambda x: self.destroy())
|
||||
player.connect("notify::art-url", lambda x, y: self.load_colors())
|
||||
self.load_colors()
|
||||
|
||||
super().__init__(
|
||||
transition_type="slide_down",
|
||||
reveal_child=False,
|
||||
css_classes=[self.get_css("media")],
|
||||
child=Widget.Overlay(
|
||||
child=Widget.Box(css_classes=[self.get_css("media-image")]),
|
||||
overlays=[
|
||||
Widget.Box(
|
||||
hexpand=True,
|
||||
vexpand=True,
|
||||
css_classes=[self.get_css("media-image-gradient")],
|
||||
),
|
||||
Widget.Icon(
|
||||
icon_name=self.get_player_icon(),
|
||||
pixel_size=22,
|
||||
halign="start",
|
||||
valign="start",
|
||||
css_classes=[self.get_css("media-player-icon")],
|
||||
),
|
||||
Widget.Box(
|
||||
vertical=True,
|
||||
hexpand=True,
|
||||
css_classes=[self.get_css("media-content")],
|
||||
child=[
|
||||
Widget.Box(
|
||||
vexpand=True,
|
||||
valign="center",
|
||||
child=[
|
||||
Widget.Box(
|
||||
hexpand=True,
|
||||
vertical=True,
|
||||
child=[
|
||||
Widget.Label(
|
||||
ellipsize="end",
|
||||
label=player.bind("title"),
|
||||
max_width_chars=30,
|
||||
halign="start",
|
||||
css_classes=[
|
||||
self.get_css("media-title")
|
||||
],
|
||||
),
|
||||
Widget.Label(
|
||||
label=player.bind("artist"),
|
||||
max_width_chars=30,
|
||||
ellipsize="end",
|
||||
halign="start",
|
||||
css_classes=[
|
||||
self.get_css("media-artist")
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Widget.Button(
|
||||
child=Widget.Icon(
|
||||
image=player.bind(
|
||||
"playback_status",
|
||||
lambda value: "media-playback-pause-symbolic"
|
||||
if value == "Playing"
|
||||
else "media-playback-start-symbolic",
|
||||
),
|
||||
pixel_size=18,
|
||||
),
|
||||
on_click=lambda x: asyncio.create_task(player.play_pause_async()),
|
||||
visible=player.bind("can_play"),
|
||||
css_classes=player.bind(
|
||||
"playback_status",
|
||||
lambda value: [
|
||||
self.get_css("media-playback-button"),
|
||||
"playing",
|
||||
]
|
||||
if value == "Playing"
|
||||
else [
|
||||
self.get_css("media-playback-button"),
|
||||
"paused",
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Widget.Box(
|
||||
vexpand=True,
|
||||
valign="end",
|
||||
style="padding: 1rem;",
|
||||
child=[
|
||||
Widget.Scale(
|
||||
value=player.bind("position"),
|
||||
max=player.bind("length"),
|
||||
hexpand=True,
|
||||
css_classes=[self.get_css("media-scale")],
|
||||
on_change=lambda x: asyncio.create_task(self._player.set_position_async(x.value)),
|
||||
visible=player.bind(
|
||||
"position", lambda value: value != -1
|
||||
),
|
||||
),
|
||||
Widget.Button(
|
||||
child=Widget.Icon(
|
||||
image="media-skip-backward-symbolic",
|
||||
pixel_size=20,
|
||||
),
|
||||
css_classes=[self.get_css("media-skip-button")],
|
||||
on_click=lambda x: asyncio.create_task(player.previous_async()),
|
||||
visible=player.bind("can_go_previous"),
|
||||
style="margin-left: 1rem;",
|
||||
),
|
||||
Widget.Button(
|
||||
child=Widget.Icon(
|
||||
image="media-skip-forward-symbolic",
|
||||
pixel_size=20,
|
||||
),
|
||||
css_classes=[self.get_css("media-skip-button")],
|
||||
on_click=lambda x: asyncio.create_task(player.next_async()),
|
||||
visible=player.bind("can_go_next"),
|
||||
style="margin-left: 1rem;",
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def get_player_icon(self) -> str:
|
||||
if self._player.desktop_entry == "firefox":
|
||||
return PLAYER_ICONS["firefox"]
|
||||
elif self._player.desktop_entry == "spotify":
|
||||
return PLAYER_ICONS["spotify"]
|
||||
elif self._player.track_id is not None:
|
||||
if "chromium" in self._player.track_id or "chrome" in self._player.track_id:
|
||||
return PLAYER_ICONS["chrome"]
|
||||
|
||||
return PLAYER_ICONS[None]
|
||||
|
||||
def destroy(self) -> None:
|
||||
self.set_reveal_child(False)
|
||||
Utils.Timeout(self.transition_duration, super().unparent)
|
||||
|
||||
def get_css(self, class_name: str) -> str:
|
||||
return f"{class_name}-{self._player.desktop_entry}"
|
||||
|
||||
def load_colors(self) -> None:
|
||||
if not self._player.art_url:
|
||||
art_url = MEDIA_ART_FALLBACK
|
||||
else:
|
||||
art_url = self._player.art_url
|
||||
|
||||
try:
|
||||
app.remove_css(self._colors_path)
|
||||
except StylePathNotFoundError:
|
||||
pass
|
||||
|
||||
colors = material.get_colors_from_img(art_url, True)
|
||||
colors["art_url"] = art_url
|
||||
colors["desktop_entry"] = self._player.desktop_entry
|
||||
material.render_template(
|
||||
colors, input_file=MEDIA_TEMPLATE, output_file=self._colors_path
|
||||
)
|
||||
app.apply_css(self._colors_path)
|
||||
|
||||
|
||||
class Media(Widget.Box):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
vertical=True,
|
||||
setup=lambda self: mpris.connect(
|
||||
"player_added", lambda x, player: self.__add_player(player)
|
||||
),
|
||||
css_classes=["rec-unset"],
|
||||
)
|
||||
|
||||
def __add_player(self, obj: MprisPlayer) -> None:
|
||||
player = Player(obj)
|
||||
self.append(player)
|
||||
player.set_reveal_child(True)
|
||||
@@ -0,0 +1,84 @@
|
||||
// JINJA2 TEMPLATE
|
||||
// WARNING: Do not import this file in style.scss
|
||||
|
||||
.media-{{ desktop_entry }} {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.media-image-{{ desktop_entry }} {
|
||||
background-image: url('file:///{{ art_url }}');
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
min-height: 11rem;
|
||||
border-radius: 1.5rem;
|
||||
}
|
||||
|
||||
.media-image-gradient-{{ desktop_entry }} {
|
||||
border-radius: 1.3rem;
|
||||
background: linear-gradient(90deg, rgba({{ onPrimary }}, 0.9) 0%, rgba({{ onPrimary }}, 0.4) 50%, rgba({{ onPrimary }}, 0.9) 100%);
|
||||
}
|
||||
|
||||
.media-scale-{{ desktop_entry }} trough,
|
||||
.media-scale-{{ desktop_entry }} trough highlight {
|
||||
background-color: rgba({{ onSurface }}, 0.2);
|
||||
border-radius: 1rem;
|
||||
min-height: 0.2rem;
|
||||
}
|
||||
|
||||
|
||||
.media-scale-{{ desktop_entry }} trough highlight {
|
||||
background-color: {{ onSurface }};
|
||||
}
|
||||
|
||||
.media-scale-{{ desktop_entry }} slider {
|
||||
background-color: {{ onSurface }};
|
||||
padding: 0.5rem 0.1rem;
|
||||
margin: -0.5rem -0.1rem;
|
||||
}
|
||||
|
||||
.media-title-{{ desktop_entry }} {
|
||||
color: {{ onSurface }};
|
||||
}
|
||||
|
||||
.media-artist-{{ desktop_entry }} {
|
||||
color: {{ onSurfaceVariant }};
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
.media-content-{{ desktop_entry }} {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.media-playback-button-{{ desktop_entry }} {
|
||||
background-color: {{ primary }};
|
||||
color: {{ onPrimary }};
|
||||
border-radius: 4rem;
|
||||
min-width: 2.7rem;
|
||||
min-height: 2.7rem;
|
||||
transition: 0.3s;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten({{ primary }}, 5%);
|
||||
}
|
||||
|
||||
&.playing {
|
||||
border-radius: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.media-player-icon-{{ desktop_entry }} {
|
||||
color: {{ primary }};
|
||||
padding: 1rem;
|
||||
padding-top: 1.2rem;
|
||||
}
|
||||
|
||||
.media-skip-button-{{ desktop_entry }} {
|
||||
transition: 0.3s;
|
||||
color: {{ onSurface }};
|
||||
&:hover {
|
||||
color: {{ primary }};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
from ignis.widgets import Widget
|
||||
from ignis.services.notifications import Notification, NotificationService
|
||||
from ignis.utils import Utils
|
||||
from gi.repository import GLib # type: ignore
|
||||
from ...shared_widgets import NotificationWidget
|
||||
|
||||
notifications = NotificationService.get_default()
|
||||
|
||||
|
||||
class Popup(Widget.Revealer):
|
||||
def __init__(self, notification: Notification, **kwargs):
|
||||
widget = NotificationWidget(notification)
|
||||
super().__init__(child=widget, transition_type="slide_down", **kwargs)
|
||||
|
||||
notification.connect("closed", lambda x: self.destroy())
|
||||
|
||||
def destroy(self):
|
||||
self.reveal_child = False
|
||||
Utils.Timeout(self.transition_duration, self.unparent)
|
||||
|
||||
|
||||
class NotificationList(Widget.Box):
|
||||
__gtype_name__ = "NotificationList"
|
||||
|
||||
def __init__(self):
|
||||
loading_notifications_label = Widget.Label(
|
||||
label="Loading notifications...",
|
||||
valign="center",
|
||||
vexpand=True,
|
||||
css_classes=["notification-center-info-label"],
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
vertical=True,
|
||||
child=[loading_notifications_label],
|
||||
vexpand=True,
|
||||
css_classes=["rec-unset"],
|
||||
setup=lambda self: notifications.connect(
|
||||
"notified",
|
||||
lambda x, notification: self.__on_notified(notification),
|
||||
),
|
||||
)
|
||||
|
||||
Utils.ThreadTask(
|
||||
self.__load_notifications,
|
||||
lambda result: self.set_child(result),
|
||||
).run()
|
||||
|
||||
def __on_notified(self, notification: Notification) -> None:
|
||||
notify = Popup(notification)
|
||||
self.prepend(notify)
|
||||
notify.reveal_child = True
|
||||
|
||||
def __load_notifications(self) -> list[Widget.Label | Popup]:
|
||||
widgets: list[Widget.Label | Popup] = []
|
||||
for i in notifications.notifications:
|
||||
GLib.idle_add(lambda i=i: widgets.append(Popup(i, reveal_child=True)))
|
||||
|
||||
widgets.append(
|
||||
Widget.Label(
|
||||
label="No notifications",
|
||||
valign="center",
|
||||
vexpand=True,
|
||||
visible=notifications.bind(
|
||||
"notifications", lambda value: len(value) == 0
|
||||
),
|
||||
css_classes=["notification-center-info-label"],
|
||||
)
|
||||
)
|
||||
return widgets
|
||||
|
||||
|
||||
class NotificationCenter(Widget.Box):
|
||||
__gtype_name__ = "NotificationCenter"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
vertical=True,
|
||||
vexpand=True,
|
||||
css_classes=["notification-center"],
|
||||
child=[
|
||||
Widget.Box(
|
||||
css_classes=["notification-center-header", "rec-unset"],
|
||||
child=[
|
||||
Widget.Label(
|
||||
label=notifications.bind(
|
||||
"notifications", lambda value: str(len(value))
|
||||
),
|
||||
css_classes=["notification-count"],
|
||||
),
|
||||
Widget.Label(
|
||||
label="notifications",
|
||||
css_classes=["notification-header-label"],
|
||||
),
|
||||
Widget.Button(
|
||||
child=Widget.Label(label="Clear all"),
|
||||
halign="end",
|
||||
hexpand=True,
|
||||
on_click=lambda x: notifications.clear_all(),
|
||||
css_classes=["notification-clear-all"],
|
||||
),
|
||||
],
|
||||
),
|
||||
Widget.Scroll(
|
||||
child=NotificationList(),
|
||||
vexpand=True,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
from .quick_settings import QuickSettings
|
||||
|
||||
__all__ = ["QuickSettings"]
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,87 @@
|
||||
from ignis.widgets import Widget
|
||||
from ...qs_button import QSButton
|
||||
from ...menu import Menu
|
||||
from ....shared_widgets import ToggleBox
|
||||
from ignis.services.bluetooth import BluetoothService, BluetoothDevice
|
||||
|
||||
bluetooth = BluetoothService.get_default()
|
||||
|
||||
|
||||
class BluetoothDeviceItem(Widget.Button):
|
||||
def __init__(self, device: BluetoothDevice):
|
||||
super().__init__(
|
||||
css_classes=["network-item", "unset"],
|
||||
on_click=lambda x: device.disconnect_from()
|
||||
if device.connected
|
||||
else device.connect_to(),
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
Widget.Icon(
|
||||
image=device.bind("icon_name"),
|
||||
),
|
||||
Widget.Label(
|
||||
label=device.alias,
|
||||
halign="start",
|
||||
css_classes=["wifi-network-label"],
|
||||
),
|
||||
Widget.Icon(
|
||||
image="object-select-symbolic",
|
||||
halign="end",
|
||||
hexpand=True,
|
||||
visible=device.bind("connected"),
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class BluetoothMenu(Menu):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name="bluetooth",
|
||||
child=[
|
||||
ToggleBox(
|
||||
label="Bluetooth",
|
||||
active=bluetooth.powered,
|
||||
on_change=lambda x, state: bluetooth.set_powered(state),
|
||||
css_classes=["network-header-box"],
|
||||
),
|
||||
Widget.Box(
|
||||
vertical=True,
|
||||
child=bluetooth.bind(
|
||||
"devices",
|
||||
transform=lambda value: [BluetoothDeviceItem(i) for i in value],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class BluetoothButton(QSButton):
|
||||
def __init__(self):
|
||||
menu = BluetoothMenu()
|
||||
|
||||
def get_label(devices: list[BluetoothDevice]) -> str:
|
||||
if len(devices) == 0:
|
||||
return "Bluetooth"
|
||||
elif len(devices) == 1:
|
||||
return devices[0].alias
|
||||
else:
|
||||
return f"{len(devices)} pairs"
|
||||
|
||||
def toggle_menu(x) -> None:
|
||||
bluetooth.set_setup_mode(True)
|
||||
menu.toggle()
|
||||
|
||||
super().__init__(
|
||||
label=bluetooth.bind("connected_devices", get_label),
|
||||
icon_name="bluetooth-active-symbolic",
|
||||
on_activate=toggle_menu,
|
||||
on_deactivate=toggle_menu,
|
||||
active=bluetooth.bind("powered"),
|
||||
menu=menu,
|
||||
)
|
||||
|
||||
|
||||
def bluetooth_control() -> list[QSButton]:
|
||||
return [] if bluetooth.state == "absent" else [BluetoothButton()]
|
||||
@@ -0,0 +1,15 @@
|
||||
from ...qs_button import QSButton
|
||||
from user_options import user_options
|
||||
|
||||
|
||||
class DarkModeButton(QSButton):
|
||||
__gtype_name__ = "DarkModeButton"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
label="Dark",
|
||||
icon_name="night-light-symbolic",
|
||||
on_activate=lambda x: user_options.material.set_dark_mode(True),
|
||||
on_deactivate=lambda x: user_options.material.set_dark_mode(False),
|
||||
active=user_options.material.bind("dark_mode"),
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
from ...qs_button import QSButton
|
||||
from ignis.services.notifications import NotificationService
|
||||
from ignis.options import options
|
||||
|
||||
notifications = NotificationService.get_default()
|
||||
|
||||
|
||||
class DNDButton(QSButton):
|
||||
__gtype_name__ = "DNDButton"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
label=options.notifications.bind(
|
||||
"dnd", lambda value: "Silent" if value else "Noisy"
|
||||
),
|
||||
icon_name=options.notifications.bind(
|
||||
"dnd",
|
||||
transform=lambda value: "notification-disabled-symbolic"
|
||||
if value
|
||||
else "notification-symbolic",
|
||||
),
|
||||
on_activate=lambda x: self.__activate(True),
|
||||
on_deactivate=lambda x: self.__activate(False),
|
||||
active=options.notifications.bind("dnd"),
|
||||
)
|
||||
|
||||
def __activate(self, state: bool) -> None:
|
||||
options.notifications.dnd = state
|
||||
@@ -0,0 +1,102 @@
|
||||
import asyncio
|
||||
from ignis.widgets import Widget
|
||||
from ignis.utils import Utils
|
||||
from ...qs_button import QSButton
|
||||
from ...menu import Menu
|
||||
from ignis.services.network import NetworkService, EthernetDevice
|
||||
|
||||
network = NetworkService.get_default()
|
||||
|
||||
|
||||
class EthernetConnectionItem(Widget.Button):
|
||||
def __init__(self, device: EthernetDevice):
|
||||
super().__init__(
|
||||
css_classes=["network-item", "unset"],
|
||||
on_click=lambda x: asyncio.create_task(device.disconnect_from())
|
||||
if device.is_connected
|
||||
else asyncio.create_task(device.connect_to()),
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
Widget.Icon(image="network-wired-symbolic"),
|
||||
Widget.Label(
|
||||
label=device.name,
|
||||
ellipsize="end",
|
||||
max_width_chars=20,
|
||||
halign="start",
|
||||
),
|
||||
Widget.Button(
|
||||
child=Widget.Label(
|
||||
label=device.bind(
|
||||
"is_connected",
|
||||
lambda value: "Disconnect" if value else "Connect",
|
||||
)
|
||||
),
|
||||
css_classes=["connect-label", "unset"],
|
||||
halign="end",
|
||||
hexpand=True,
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class EthernetMenu(Menu):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name="ethernet",
|
||||
child=[
|
||||
Widget.Box(
|
||||
css_classes=["network-header-box"],
|
||||
child=[
|
||||
Widget.Icon(icon_name="network-wired-symbolic", pixel_size=28),
|
||||
Widget.Label(
|
||||
label="Wired connections",
|
||||
css_classes=["network-header-label"],
|
||||
),
|
||||
],
|
||||
),
|
||||
Widget.Box(
|
||||
vertical=True,
|
||||
child=network.ethernet.bind(
|
||||
"devices",
|
||||
lambda value: [EthernetConnectionItem(i) for i in value],
|
||||
),
|
||||
),
|
||||
Widget.Separator(),
|
||||
Widget.Button(
|
||||
css_classes=["network-item", "unset"],
|
||||
style="margin-bottom: 0;",
|
||||
on_click=lambda x: asyncio.create_task(Utils.exec_sh_async("nm-connection-editor")),
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
Widget.Icon(image="preferences-system-symbolic"),
|
||||
Widget.Label(
|
||||
label="Network Settings",
|
||||
halign="start",
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class EthernetButton(QSButton):
|
||||
def __init__(self):
|
||||
menu = EthernetMenu()
|
||||
|
||||
super().__init__(
|
||||
label="Wired",
|
||||
icon_name="network-wired-symbolic",
|
||||
on_activate=lambda x: menu.toggle(),
|
||||
on_deactivate=lambda x: menu.toggle(),
|
||||
menu=menu,
|
||||
active=network.ethernet.bind("is_connected"),
|
||||
)
|
||||
|
||||
|
||||
def ethernet_control() -> list[QSButton]:
|
||||
if len(network.ethernet.devices) > 0:
|
||||
return [EthernetButton()]
|
||||
else:
|
||||
return []
|
||||
@@ -0,0 +1,68 @@
|
||||
from ignis.widgets import Widget
|
||||
from .wifi import wifi_control
|
||||
from .record import RecordButton
|
||||
from .dnd import DNDButton
|
||||
from .dark_mode import DarkModeButton
|
||||
from .ethernet import ethernet_control
|
||||
from .vpn import vpn_control
|
||||
from .bluetooth import bluetooth_control
|
||||
from ...qs_button import QSButton
|
||||
from ignis.services.network import NetworkService
|
||||
|
||||
network = NetworkService.get_default()
|
||||
|
||||
|
||||
class QuickSettings(Widget.Box):
|
||||
def __init__(self):
|
||||
super().__init__(vertical=True, css_classes=["qs-main-box"])
|
||||
network.wifi.connect("notify::devices", lambda x, y: self.__refresh())
|
||||
network.ethernet.connect("notify::devices", lambda x, y: self.__refresh())
|
||||
network.vpn.connect("notify::connections", lambda x, y: self.__refresh())
|
||||
|
||||
self.__refresh()
|
||||
|
||||
def __refresh(self) -> None:
|
||||
self.child = []
|
||||
self.__configure()
|
||||
|
||||
def __configure(self) -> None:
|
||||
self.__qs_fabric(
|
||||
*wifi_control(),
|
||||
*ethernet_control(),
|
||||
*vpn_control(),
|
||||
*bluetooth_control(),
|
||||
DNDButton(),
|
||||
DarkModeButton(),
|
||||
RecordButton(),
|
||||
)
|
||||
|
||||
def __qs_fabric(self, *buttons: QSButton) -> None:
|
||||
for i in range(0, len(buttons), 2):
|
||||
self.__add_row(buttons, i)
|
||||
|
||||
def __add_row(self, buttons: tuple[QSButton, ...], i: int) -> None:
|
||||
row = Widget.Box(homogeneous=True)
|
||||
if len(self.child) > 0:
|
||||
row.style = "margin-top: 0.5rem;"
|
||||
|
||||
self.append(row)
|
||||
|
||||
button1 = buttons[i]
|
||||
|
||||
self.__add_button(row, button1, buttons, i)
|
||||
|
||||
if i + 1 < len(buttons):
|
||||
button2 = buttons[i + 1]
|
||||
button2.style = "margin-left: 0.5rem;"
|
||||
self.__add_button(row, button2, buttons, i)
|
||||
|
||||
def __add_button(
|
||||
self, row: Widget.Box, button: QSButton, buttons: tuple[QSButton, ...], i: int
|
||||
) -> None:
|
||||
row.append(button)
|
||||
|
||||
if button.menu:
|
||||
self.append(button.menu)
|
||||
|
||||
if i == len(buttons) - 1 or i == len(buttons) - 2:
|
||||
button.menu.box.add_css_class("control-center-menu-last-row")
|
||||
@@ -0,0 +1,102 @@
|
||||
from ignis.widgets import Widget
|
||||
from ...qs_button import QSButton
|
||||
from ...menu import Menu
|
||||
from ignis.services.recorder import RecorderService
|
||||
|
||||
recorder = RecorderService.get_default()
|
||||
|
||||
|
||||
class RecordMenu(Menu):
|
||||
def __init__(self):
|
||||
self._audio_switch = Widget.Switch(halign="end", hexpand=True, valign="center")
|
||||
self._dropdown = Widget.DropDown(
|
||||
items=["Internal audio", "Microphone", "Both sources"],
|
||||
css_classes=["record-dropdown"],
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
name="recording",
|
||||
child=[
|
||||
Widget.Icon(
|
||||
image="media-record-symbolic",
|
||||
pixel_size=36,
|
||||
halign="center",
|
||||
css_classes=["record-icon"],
|
||||
),
|
||||
Widget.Label(
|
||||
label="Start recording?",
|
||||
halign="center",
|
||||
style="font-size: 1.2rem;",
|
||||
),
|
||||
Widget.Box(
|
||||
style="margin-top: 0.5rem;",
|
||||
child=[
|
||||
Widget.Icon(
|
||||
image="microphone-sensitivity-medium-symbolic",
|
||||
pixel_size=20,
|
||||
style="margin-right: 0.5rem;",
|
||||
),
|
||||
Widget.Box(
|
||||
vertical=True,
|
||||
child=[
|
||||
Widget.Label(
|
||||
label="Record audio",
|
||||
style="font-size: 1.1rem;",
|
||||
halign="start",
|
||||
),
|
||||
self._dropdown,
|
||||
],
|
||||
),
|
||||
self._audio_switch,
|
||||
],
|
||||
),
|
||||
Widget.Box(
|
||||
style="margin-top: 1rem;",
|
||||
child=[
|
||||
Widget.Button(
|
||||
child=Widget.Label(label="Cancel"),
|
||||
css_classes=["record-cancel-button", "unset"],
|
||||
on_click=lambda x: self.set_reveal_child(False), # type: ignore
|
||||
),
|
||||
Widget.Button(
|
||||
child=Widget.Label(label="Start recording"),
|
||||
halign="end",
|
||||
hexpand=True,
|
||||
css_classes=["record-start-button", "unset"],
|
||||
on_click=lambda x: self.__start_recording(), # type: ignore
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
def __start_recording(self) -> None:
|
||||
self.set_reveal_child(False)
|
||||
microphone = False
|
||||
internal = False
|
||||
if self._audio_switch.active:
|
||||
if self._dropdown.selected == "Internal audio":
|
||||
internal = True
|
||||
elif self._dropdown.selected == "Microphone":
|
||||
microphone = True
|
||||
else:
|
||||
internal = True
|
||||
microphone = True
|
||||
|
||||
recorder.start_recording(
|
||||
record_microphone=microphone, record_internal_audio=internal
|
||||
)
|
||||
|
||||
|
||||
class RecordButton(QSButton):
|
||||
def __init__(self):
|
||||
record_menu = RecordMenu()
|
||||
|
||||
super().__init__(
|
||||
label="Recording",
|
||||
icon_name="media-record-symbolic",
|
||||
on_activate=lambda x: record_menu.toggle(),
|
||||
on_deactivate=lambda x: recorder.stop_recording(),
|
||||
active=recorder.bind("active"),
|
||||
menu=record_menu,
|
||||
)
|
||||
@@ -0,0 +1,112 @@
|
||||
import asyncio
|
||||
from ignis.widgets import Widget
|
||||
from ignis.utils import Utils
|
||||
from ...qs_button import QSButton
|
||||
from ...menu import Menu
|
||||
from ignis.services.network import NetworkService, VpnConnection
|
||||
|
||||
|
||||
network = NetworkService.get_default()
|
||||
|
||||
|
||||
class VpnNetworkItem(Widget.Button):
|
||||
def __init__(self, conn: VpnConnection):
|
||||
super().__init__(
|
||||
css_classes=["network-item", "unset"],
|
||||
on_click=lambda x: asyncio.create_task(conn.toggle_connection()),
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
Widget.Label(
|
||||
label=conn.name,
|
||||
ellipsize="end",
|
||||
max_width_chars=20,
|
||||
halign="start",
|
||||
),
|
||||
Widget.Button(
|
||||
child=Widget.Label(
|
||||
label=conn.bind(
|
||||
"is_connected",
|
||||
lambda value: "Disconnect" if value else "Connect",
|
||||
)
|
||||
),
|
||||
css_classes=["connect-label", "unset"],
|
||||
halign="end",
|
||||
hexpand=True,
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class VpnMenu(Menu):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name="vpn",
|
||||
child=[
|
||||
Widget.Box(
|
||||
css_classes=["network-header-box"],
|
||||
child=[
|
||||
Widget.Icon(icon_name="network-vpn-symbolic", pixel_size=28),
|
||||
Widget.Label(
|
||||
label="VPN connections",
|
||||
css_classes=["network-header-label"],
|
||||
),
|
||||
],
|
||||
),
|
||||
Widget.Box(
|
||||
vertical=True,
|
||||
child=network.vpn.bind(
|
||||
"connections",
|
||||
transform=lambda value: [VpnNetworkItem(i) for i in value],
|
||||
),
|
||||
),
|
||||
Widget.Separator(),
|
||||
Widget.Button(
|
||||
css_classes=["network-item", "unset"],
|
||||
on_click=lambda x: asyncio.create_task(Utils.exec_sh_async("nm-connection-editor")),
|
||||
style="margin-bottom: 0;",
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
Widget.Icon(image="preferences-system-symbolic"),
|
||||
Widget.Label(
|
||||
label="Network Manager",
|
||||
halign="start",
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class VpnButton(QSButton):
|
||||
def __init__(self):
|
||||
menu = VpnMenu()
|
||||
|
||||
def get_label(id: str) -> str:
|
||||
if id:
|
||||
return id
|
||||
else:
|
||||
return "VPN"
|
||||
|
||||
def get_icon(icon_name: str) -> str:
|
||||
if network.vpn.is_connected:
|
||||
return icon_name
|
||||
else:
|
||||
return "network-vpn-symbolic"
|
||||
|
||||
super().__init__(
|
||||
label=network.vpn.bind("active_vpn_id", get_label),
|
||||
icon_name=network.vpn.bind("icon-name", get_icon),
|
||||
on_activate=lambda x: menu.toggle(),
|
||||
on_deactivate=lambda x: menu.toggle(),
|
||||
active=network.vpn.bind("is-connected"),
|
||||
menu=menu,
|
||||
)
|
||||
|
||||
|
||||
def vpn_control() -> list[QSButton]:
|
||||
if len(network.vpn.connections) > 0:
|
||||
return [VpnButton()]
|
||||
else:
|
||||
return []
|
||||
@@ -0,0 +1,107 @@
|
||||
import asyncio
|
||||
from ignis.widgets import Widget
|
||||
from ignis.utils import Utils
|
||||
from ...qs_button import QSButton
|
||||
from ...menu import Menu
|
||||
from ....shared_widgets import ToggleBox
|
||||
from ignis.services.network import NetworkService, WifiAccessPoint, WifiDevice
|
||||
|
||||
network = NetworkService.get_default()
|
||||
|
||||
|
||||
class WifiNetworkItem(Widget.Button):
|
||||
def __init__(self, access_point: WifiAccessPoint):
|
||||
super().__init__(
|
||||
css_classes=["network-item", "unset"],
|
||||
on_click=lambda x: asyncio.create_task(access_point.connect_to_graphical()),
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
Widget.Icon(
|
||||
image=access_point.bind(
|
||||
"strength", transform=lambda value: access_point.icon_name
|
||||
),
|
||||
),
|
||||
Widget.Label(
|
||||
label=access_point.ssid,
|
||||
halign="start",
|
||||
),
|
||||
Widget.Icon(
|
||||
image="object-select-symbolic",
|
||||
halign="end",
|
||||
hexpand=True,
|
||||
visible=access_point.bind("is_connected"),
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class WifiMenu(Menu):
|
||||
def __init__(self, device: WifiDevice):
|
||||
super().__init__(
|
||||
name="wifi",
|
||||
child=[
|
||||
ToggleBox(
|
||||
label="Wi-Fi",
|
||||
active=network.wifi.enabled,
|
||||
on_change=lambda x, state: network.wifi.set_enabled(state),
|
||||
css_classes=["network-header-box"],
|
||||
),
|
||||
Widget.Box(
|
||||
vertical=True,
|
||||
child=device.bind(
|
||||
"access_points",
|
||||
transform=lambda value: [WifiNetworkItem(i) for i in value],
|
||||
),
|
||||
),
|
||||
Widget.Separator(),
|
||||
Widget.Button(
|
||||
css_classes=["network-item", "unset"],
|
||||
on_click=lambda x: asyncio.create_task(Utils.exec_sh_async("nm-connection-editor")),
|
||||
style="margin-bottom: 0;",
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
Widget.Icon(image="preferences-system-symbolic"),
|
||||
Widget.Label(
|
||||
label="Network Settings",
|
||||
halign="start",
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class WifiButton(QSButton):
|
||||
def __init__(self, device: WifiDevice):
|
||||
menu = WifiMenu(device)
|
||||
|
||||
def get_label(ssid: str) -> str:
|
||||
if ssid:
|
||||
return ssid
|
||||
else:
|
||||
return "Wi-Fi"
|
||||
|
||||
def get_icon(icon_name: str) -> str:
|
||||
if device.ap.is_connected:
|
||||
return icon_name
|
||||
else:
|
||||
return "network-wireless-symbolic"
|
||||
|
||||
def toggle_list(x) -> None:
|
||||
asyncio.create_task(device.scan())
|
||||
menu.toggle()
|
||||
|
||||
super().__init__(
|
||||
label=device.ap.bind("ssid", get_label),
|
||||
icon_name=device.ap.bind("icon-name", get_icon),
|
||||
on_activate=toggle_list,
|
||||
on_deactivate=toggle_list,
|
||||
active=network.wifi.bind("enabled"),
|
||||
menu=menu,
|
||||
)
|
||||
|
||||
|
||||
def wifi_control() -> list[QSButton]:
|
||||
return [WifiButton(dev) for dev in network.wifi.devices]
|
||||
@@ -0,0 +1,71 @@
|
||||
import os
|
||||
from ignis.widgets import Widget
|
||||
from ignis.utils import Utils
|
||||
from ignis.app import IgnisApp
|
||||
from ignis.services.fetch import FetchService
|
||||
from user_options import user_options
|
||||
|
||||
fetch = FetchService.get_default()
|
||||
app = IgnisApp.get_default()
|
||||
|
||||
|
||||
def format_uptime(value: tuple[int, int, int, int]) -> str:
|
||||
days, hours, minutes, seconds = value
|
||||
if days:
|
||||
return f"up {days:02}:{hours:02}:{minutes:02}"
|
||||
else:
|
||||
return f"up {hours:02}:{minutes:02}"
|
||||
|
||||
|
||||
class User(Widget.Box):
|
||||
def __init__(self):
|
||||
user_image = Widget.Picture(
|
||||
image=user_options.user.bind(
|
||||
"avatar",
|
||||
lambda value: "user-info" if not os.path.exists(value) else value,
|
||||
),
|
||||
width=44,
|
||||
height=44,
|
||||
content_fit="cover",
|
||||
style="border-radius: 10rem;",
|
||||
)
|
||||
|
||||
username = Widget.Box(
|
||||
child=[
|
||||
Widget.Label(
|
||||
label=os.getenv("USER"), css_classes=["user-name"], halign="start"
|
||||
),
|
||||
Widget.Label(
|
||||
label=Utils.Poll(
|
||||
timeout=60 * 1000, callback=lambda x: fetch.uptime
|
||||
).bind("output", lambda value: format_uptime(value)),
|
||||
halign="start",
|
||||
css_classes=["user-name-secondary"],
|
||||
),
|
||||
],
|
||||
vertical=True,
|
||||
css_classes=["user-name-box"],
|
||||
)
|
||||
|
||||
settings_button = Widget.Button(
|
||||
child=Widget.Icon(image="emblem-system-symbolic", pixel_size=20),
|
||||
halign="end",
|
||||
hexpand=True,
|
||||
css_classes=["user-settings", "unset"],
|
||||
on_click=lambda x: self.__on_settings_button_click(),
|
||||
)
|
||||
|
||||
power_button = Widget.Button(
|
||||
child=Widget.Icon(image="system-shutdown-symbolic", pixel_size=20),
|
||||
halign="end",
|
||||
css_classes=["user-power", "unset"],
|
||||
on_click=lambda x: app.toggle_window("ignis_POWERMENU"),
|
||||
)
|
||||
super().__init__(
|
||||
child=[user_image, username, settings_button, power_button],
|
||||
css_classes=["user"],
|
||||
)
|
||||
|
||||
def __on_settings_button_click(self) -> None:
|
||||
window = app.get_window("ignis_SETTINGS")
|
||||
window.visible = not window.visible # type: ignore
|
||||
@@ -0,0 +1,128 @@
|
||||
import asyncio
|
||||
from ignis.widgets import Widget
|
||||
from ignis.utils import Utils
|
||||
from ignis.services.audio import AudioService, Stream
|
||||
from typing import Literal
|
||||
from ..menu import Menu
|
||||
from ...shared_widgets import MaterialVolumeSlider
|
||||
|
||||
audio = AudioService.get_default()
|
||||
|
||||
AUDIO_TYPES = {
|
||||
"speaker": {"menu_icon": "audio-headphones-symbolic", "menu_label": "Sound Output"},
|
||||
"microphone": {
|
||||
"menu_icon": "microphone-sensitivity-high-symbolic",
|
||||
"menu_label": "Sound Input",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class DeviceItem(Widget.Button):
|
||||
def __init__(self, stream: Stream, _type: Literal["speaker", "microphone"]):
|
||||
super().__init__(
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
Widget.Icon(image="audio-card-symbolic"),
|
||||
Widget.Label(
|
||||
label=stream.description,
|
||||
ellipsize="end",
|
||||
max_width_chars=30,
|
||||
halign="start",
|
||||
css_classes=["volume-entry-label"],
|
||||
),
|
||||
Widget.Icon(
|
||||
image="object-select-symbolic",
|
||||
halign="end",
|
||||
hexpand=True,
|
||||
visible=stream.bind("is_default"),
|
||||
),
|
||||
]
|
||||
),
|
||||
css_classes=["volume-entry", "unset"],
|
||||
hexpand=True,
|
||||
setup=lambda self: stream.connect("removed", lambda x: self.unparent()),
|
||||
on_click=lambda x: setattr(audio, _type, stream),
|
||||
)
|
||||
|
||||
|
||||
class DeviceMenu(Menu):
|
||||
def __init__(self, _type: Literal["speaker", "microphone"], style: str = ""):
|
||||
data = AUDIO_TYPES[_type]
|
||||
|
||||
super().__init__(
|
||||
name=f"volume-{_type}",
|
||||
child=[
|
||||
Widget.Box(
|
||||
child=[
|
||||
Widget.Icon(image=data["menu_icon"], pixel_size=24),
|
||||
Widget.Label(
|
||||
label=data["menu_label"],
|
||||
halign="start",
|
||||
css_classes=["volume-entry-list-header-label"],
|
||||
),
|
||||
],
|
||||
css_classes=["volume-entry-list-header-box"],
|
||||
),
|
||||
Widget.Box(
|
||||
vertical=True,
|
||||
setup=lambda self: audio.connect(
|
||||
f"{_type}-added",
|
||||
lambda x, stream: self.append(DeviceItem(stream, _type)),
|
||||
),
|
||||
),
|
||||
Widget.Separator(css_classes=["volume-entry-list-separator"]),
|
||||
Widget.Button(
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
Widget.Icon(image="preferences-system-symbolic"),
|
||||
Widget.Label(
|
||||
label="Sound Settings",
|
||||
halign="start",
|
||||
css_classes=["volume-entry-label"],
|
||||
),
|
||||
]
|
||||
),
|
||||
css_classes=["volume-entry", "unset"],
|
||||
style="margin-bottom: 0;",
|
||||
on_click=lambda x: asyncio.create_task(Utils.exec_sh_async("pavucontrol")),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
self.box.add_css_class(f"volume-menubox-{_type}")
|
||||
|
||||
|
||||
class VolumeSlider(Widget.Box):
|
||||
def __init__(self, _type: Literal["speaker", "microphone"]):
|
||||
stream = getattr(audio, _type)
|
||||
|
||||
icon = Widget.Button(
|
||||
child=Widget.Icon(
|
||||
image=stream.bind("icon_name"),
|
||||
pixel_size=18,
|
||||
),
|
||||
css_classes=["material-slider-icon", "unset", "hover-surface"],
|
||||
on_click=lambda x: stream.set_is_muted(not stream.is_muted),
|
||||
)
|
||||
|
||||
device_menu = DeviceMenu(_type=_type)
|
||||
|
||||
scale = MaterialVolumeSlider(
|
||||
stream=stream,
|
||||
on_change=lambda x: stream.set_volume(x.value),
|
||||
sensitive=stream.bind("is_muted", lambda value: not value),
|
||||
)
|
||||
|
||||
arrow = Widget.Button(
|
||||
child=Widget.Arrow(pixel_size=20, rotated=device_menu.bind("reveal_child")),
|
||||
css_classes=["material-slider-arrow", "hover-surface"],
|
||||
on_click=lambda x: device_menu.toggle(),
|
||||
)
|
||||
super().__init__(
|
||||
vertical=True,
|
||||
child=[
|
||||
Widget.Box(child=[icon, scale, arrow]),
|
||||
device_menu,
|
||||
],
|
||||
css_classes=[f"volume-mainbox-{_type}"],
|
||||
)
|
||||
@@ -1,212 +1,3 @@
|
||||
import re
|
||||
from ignis.widgets import Widget
|
||||
from ignis.app import IgnisApp
|
||||
from ignis.services.applications import (
|
||||
ApplicationsService,
|
||||
Application,
|
||||
ApplicationAction,
|
||||
)
|
||||
from ignis.utils import Utils
|
||||
from gi.repository import Gio # type: ignore
|
||||
from .launcher import Launcher
|
||||
|
||||
app = IgnisApp.get_default()
|
||||
|
||||
applications = ApplicationsService.get_default()
|
||||
|
||||
|
||||
def is_url(url: str) -> bool:
|
||||
regex = re.compile(
|
||||
r"^(?:http|ftp)s?://" # http:// or https://
|
||||
r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|" # domain
|
||||
r"localhost|" # localhost
|
||||
r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|" # or ipv4
|
||||
r"\[?[A-F0-9]*:[A-F0-9:]+\]?)" # or ipv6
|
||||
r"(?::\d+)?" # optional port
|
||||
r"(?:/?|[/?]\S+)$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
return re.match(regex, url) is not None
|
||||
|
||||
|
||||
class LauncherAppItem(Widget.Button):
|
||||
def __init__(self, application: Application) -> None:
|
||||
self._application = application
|
||||
super().__init__(
|
||||
on_click=lambda x: self.launch(),
|
||||
on_right_click=lambda x: self._menu.popup(),
|
||||
css_classes=["launcher-app"],
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
Widget.Icon(image=application.icon, pixel_size=48),
|
||||
Widget.Label(
|
||||
label=application.name,
|
||||
ellipsize="end",
|
||||
max_width_chars=30,
|
||||
css_classes=["launcher-app-label"],
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
self.__sync_menu()
|
||||
application.connect("notify::is-pinned", lambda x, y: self.__sync_menu())
|
||||
|
||||
def launch(self) -> None:
|
||||
self._application.launch()
|
||||
app.close_window("ignis_LAUNCHER")
|
||||
|
||||
def launch_action(self, action: ApplicationAction) -> None:
|
||||
action.launch()
|
||||
app.close_window("ignis_LAUNCHER")
|
||||
|
||||
def __sync_menu(self) -> None:
|
||||
self._menu = Widget.PopoverMenu(
|
||||
items=[
|
||||
Widget.MenuItem(label="Launch", on_activate=lambda x: self.launch()),
|
||||
Widget.Separator(),
|
||||
]
|
||||
+ [
|
||||
Widget.MenuItem(
|
||||
label=i.name,
|
||||
on_activate=lambda x, action=i: self.launch_action(action),
|
||||
)
|
||||
for i in self._application.actions
|
||||
]
|
||||
+ [
|
||||
Widget.Separator(),
|
||||
Widget.MenuItem(
|
||||
label="Pin", on_activate=lambda x: self._application.pin()
|
||||
)
|
||||
if not self._application.is_pinned
|
||||
else Widget.MenuItem(
|
||||
label="Unpin", on_activate=lambda x: self._application.unpin()
|
||||
),
|
||||
]
|
||||
)
|
||||
self.child.append(self._menu)
|
||||
|
||||
|
||||
class SearchWebButton(Widget.Button):
|
||||
def __init__(self, query: str):
|
||||
self._query = query
|
||||
self._url = ""
|
||||
|
||||
browser_desktop_file = Utils.exec_sh(
|
||||
"xdg-settings get default-web-browser"
|
||||
).stdout.replace("\n", "")
|
||||
|
||||
app_info = Gio.DesktopAppInfo.new(desktop_id=browser_desktop_file)
|
||||
|
||||
icon_name = "applications-internet-symbolic"
|
||||
if app_info:
|
||||
icon_string = app_info.get_string("Icon")
|
||||
if icon_string:
|
||||
icon_name = icon_string
|
||||
|
||||
if not query.startswith(("http://", "https://")) and "." in query:
|
||||
query = "https://" + query
|
||||
|
||||
if is_url(query):
|
||||
label = f"Visit {query}"
|
||||
self._url = query
|
||||
else:
|
||||
label = "Search in Google"
|
||||
self._url = f"https://www.google.com/search?q={query.replace(' ', '+')}"
|
||||
|
||||
super().__init__(
|
||||
on_click=lambda x: self.launch(),
|
||||
css_classes=["launcher-app"],
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
Widget.Icon(image=icon_name, pixel_size=48),
|
||||
Widget.Label(
|
||||
label=label,
|
||||
css_classes=["launcher-app-label"],
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def launch(self) -> None:
|
||||
Utils.exec_sh_async(f"xdg-open {self._url}")
|
||||
app.close_window("ignis_LAUNCHER")
|
||||
|
||||
|
||||
def launcher() -> Widget.Window:
|
||||
def search(entry: Widget.Entry, app_list: Widget.Box) -> None:
|
||||
query = entry.text
|
||||
|
||||
if query == "":
|
||||
entry.grab_focus()
|
||||
app_list.visible = False
|
||||
return
|
||||
|
||||
apps = applications.search(applications.apps, query)
|
||||
if apps == []:
|
||||
app_list.child = [SearchWebButton(query)]
|
||||
else:
|
||||
app_list.visible = True
|
||||
app_list.child = [LauncherAppItem(i) for i in apps[:5]]
|
||||
|
||||
def on_open(window: Widget.Window, entry: Widget.Entry) -> None:
|
||||
if not window.visible:
|
||||
return
|
||||
|
||||
entry.text = ""
|
||||
entry.grab_focus()
|
||||
|
||||
app_list = Widget.Box(vertical=True, visible=False, style="margin-top: 1rem;")
|
||||
entry = Widget.Entry(
|
||||
hexpand=True,
|
||||
placeholder_text="Search",
|
||||
css_classes=["launcher-search"],
|
||||
on_change=lambda x: search(x, app_list),
|
||||
on_accept=lambda x: app_list.child[0].launch()
|
||||
if len(app_list.child) > 0
|
||||
else None,
|
||||
)
|
||||
|
||||
main_box = Widget.Box(
|
||||
vertical=True,
|
||||
valign="start",
|
||||
halign="center",
|
||||
css_classes=["launcher"],
|
||||
child=[
|
||||
Widget.Box(
|
||||
css_classes=["launcher-search-box"],
|
||||
child=[
|
||||
Widget.Icon(
|
||||
icon_name="system-search-symbolic",
|
||||
pixel_size=24,
|
||||
style="margin-right: 0.5rem;",
|
||||
),
|
||||
entry,
|
||||
],
|
||||
),
|
||||
app_list,
|
||||
],
|
||||
)
|
||||
|
||||
return Widget.Window(
|
||||
namespace="ignis_LAUNCHER",
|
||||
visible=False,
|
||||
popup=True,
|
||||
kb_mode="on_demand",
|
||||
# exclusivity="ignore",
|
||||
css_classes=["unset"],
|
||||
setup=lambda self: self.connect(
|
||||
"notify::visible", lambda x, y: on_open(self, entry)
|
||||
),
|
||||
anchor=["top", "right", "bottom", "left"],
|
||||
child=Widget.Overlay(
|
||||
child=Widget.Button(
|
||||
vexpand=True,
|
||||
hexpand=True,
|
||||
can_focus=False,
|
||||
css_classes=["unset"],
|
||||
on_click=lambda x: app.close_window("ignis_LAUNCHER"),
|
||||
style="background-color: rgba(0, 0, 0, 0.3);",
|
||||
),
|
||||
overlays=[main_box],
|
||||
),
|
||||
)
|
||||
__all__ = ["Launcher"]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,217 @@
|
||||
import re
|
||||
import asyncio
|
||||
from ignis.widgets import Widget
|
||||
from ignis.app import IgnisApp
|
||||
from ignis.services.applications import (
|
||||
ApplicationsService,
|
||||
Application,
|
||||
ApplicationAction,
|
||||
)
|
||||
from ignis.utils import Utils
|
||||
from gi.repository import Gio # type: ignore
|
||||
|
||||
app = IgnisApp.get_default()
|
||||
|
||||
applications = ApplicationsService.get_default()
|
||||
|
||||
TERMINAL_FORMAT = "kitty %command%"
|
||||
|
||||
|
||||
def is_url(url: str) -> bool:
|
||||
regex = re.compile(
|
||||
r"^(?:http|ftp)s?://" # http:// or https://
|
||||
r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|" # domain
|
||||
r"localhost|" # localhost
|
||||
r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|" # or ipv4
|
||||
r"\[?[A-F0-9]*:[A-F0-9:]+\]?)" # or ipv6
|
||||
r"(?::\d+)?" # optional port
|
||||
r"(?:/?|[/?]\S+)$",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
return re.match(regex, url) is not None
|
||||
|
||||
|
||||
class LauncherAppItem(Widget.Button):
|
||||
def __init__(self, application: Application) -> None:
|
||||
self._application = application
|
||||
super().__init__(
|
||||
on_click=lambda x: self.launch(),
|
||||
on_right_click=lambda x: self._menu.popup(),
|
||||
css_classes=["launcher-app"],
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
Widget.Icon(image=application.icon, pixel_size=48),
|
||||
Widget.Label(
|
||||
label=application.name,
|
||||
ellipsize="end",
|
||||
max_width_chars=30,
|
||||
css_classes=["launcher-app-label"],
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
self.__sync_menu()
|
||||
application.connect("notify::is-pinned", lambda x, y: self.__sync_menu())
|
||||
|
||||
def launch(self) -> None:
|
||||
self._application.launch(terminal_format=TERMINAL_FORMAT)
|
||||
app.close_window("ignis_LAUNCHER")
|
||||
|
||||
def launch_action(self, action: ApplicationAction) -> None:
|
||||
action.launch()
|
||||
app.close_window("ignis_LAUNCHER")
|
||||
|
||||
def __sync_menu(self) -> None:
|
||||
self._menu = Widget.PopoverMenu(
|
||||
items=[
|
||||
Widget.MenuItem(label="Launch", on_activate=lambda x: self.launch()),
|
||||
Widget.Separator(),
|
||||
]
|
||||
+ [
|
||||
Widget.MenuItem(
|
||||
label=i.name,
|
||||
on_activate=lambda x, action=i: self.launch_action(action),
|
||||
)
|
||||
for i in self._application.actions
|
||||
]
|
||||
+ [
|
||||
Widget.Separator(),
|
||||
Widget.MenuItem(
|
||||
label="Pin", on_activate=lambda x: self._application.pin()
|
||||
)
|
||||
if not self._application.is_pinned
|
||||
else Widget.MenuItem(
|
||||
label="Unpin", on_activate=lambda x: self._application.unpin()
|
||||
),
|
||||
]
|
||||
)
|
||||
self.child.append(self._menu)
|
||||
|
||||
|
||||
class SearchWebButton(Widget.Button):
|
||||
def __init__(self, query: str):
|
||||
self._query = query
|
||||
self._url = ""
|
||||
|
||||
browser_desktop_file = Utils.exec_sh(
|
||||
"xdg-settings get default-web-browser"
|
||||
).stdout.replace("\n", "")
|
||||
|
||||
app_info = Gio.DesktopAppInfo.new(desktop_id=browser_desktop_file)
|
||||
|
||||
icon_name = "applications-internet-symbolic"
|
||||
if app_info:
|
||||
icon_string = app_info.get_string("Icon")
|
||||
if icon_string:
|
||||
icon_name = icon_string
|
||||
|
||||
if not query.startswith(("http://", "https://")) and "." in query:
|
||||
query = "https://" + query
|
||||
|
||||
if is_url(query):
|
||||
label = f"Visit {query}"
|
||||
self._url = query
|
||||
else:
|
||||
label = "Search in Google"
|
||||
self._url = f"https://www.google.com/search?q={query.replace(' ', '+')}"
|
||||
|
||||
super().__init__(
|
||||
on_click=lambda x: self.launch(),
|
||||
css_classes=["launcher-app"],
|
||||
child=Widget.Box(
|
||||
child=[
|
||||
Widget.Icon(image=icon_name, pixel_size=48),
|
||||
Widget.Label(
|
||||
label=label,
|
||||
css_classes=["launcher-app-label"],
|
||||
),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def launch(self) -> None:
|
||||
asyncio.create_task(Utils.exec_sh_async(f"xdg-open {self._url}"))
|
||||
app.close_window("ignis_LAUNCHER")
|
||||
|
||||
|
||||
class Launcher(Widget.Window):
|
||||
def __init__(self):
|
||||
self._app_list = Widget.Box(
|
||||
vertical=True, visible=False, style="margin-top: 1rem;"
|
||||
)
|
||||
self._entry = Widget.Entry(
|
||||
hexpand=True,
|
||||
placeholder_text="Search",
|
||||
css_classes=["launcher-search"],
|
||||
on_change=self.__search,
|
||||
on_accept=self.__on_accept,
|
||||
)
|
||||
|
||||
main_box = Widget.Box(
|
||||
vertical=True,
|
||||
valign="start",
|
||||
halign="center",
|
||||
css_classes=["launcher"],
|
||||
child=[
|
||||
Widget.Box(
|
||||
css_classes=["launcher-search-box"],
|
||||
child=[
|
||||
Widget.Icon(
|
||||
icon_name="system-search-symbolic",
|
||||
pixel_size=24,
|
||||
style="margin-right: 0.5rem;",
|
||||
),
|
||||
self._entry,
|
||||
],
|
||||
),
|
||||
self._app_list,
|
||||
],
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
namespace="ignis_LAUNCHER",
|
||||
visible=False,
|
||||
popup=True,
|
||||
kb_mode="on_demand",
|
||||
css_classes=["unset"],
|
||||
setup=lambda self: self.connect("notify::visible", self.__on_open),
|
||||
anchor=["top", "right", "bottom", "left"],
|
||||
child=Widget.Overlay(
|
||||
child=Widget.Button(
|
||||
vexpand=True,
|
||||
hexpand=True,
|
||||
can_focus=False,
|
||||
css_classes=["unset"],
|
||||
on_click=lambda x: app.close_window("ignis_LAUNCHER"),
|
||||
style="background-color: rgba(0, 0, 0, 0.3);",
|
||||
),
|
||||
overlays=[main_box],
|
||||
),
|
||||
)
|
||||
|
||||
def __on_open(self, *args) -> None:
|
||||
if not self.visible:
|
||||
return
|
||||
|
||||
self._entry.text = ""
|
||||
self._entry.grab_focus()
|
||||
|
||||
def __on_accept(self, *args) -> None:
|
||||
if len(self._app_list.child) > 0:
|
||||
self._app_list.child[0].launch()
|
||||
|
||||
def __search(self, *args) -> None:
|
||||
query = self._entry.text
|
||||
|
||||
if query == "":
|
||||
self._entry.grab_focus()
|
||||
self._app_list.visible = False
|
||||
return
|
||||
|
||||
apps = applications.search(applications.apps, query)
|
||||
if apps == []:
|
||||
self._app_list.child = [SearchWebButton(query)]
|
||||
else:
|
||||
self._app_list.visible = True
|
||||
self._app_list.child = [LauncherAppItem(i) for i in apps[:5]]
|
||||
@@ -1,95 +1,3 @@
|
||||
from ignis.widgets import Widget
|
||||
from ignis.app import IgnisApp
|
||||
from ignis.utils import Utils
|
||||
from ignis.services.notifications import Notification, NotificationService
|
||||
from ..control_center.notification_center import NotificationWidget
|
||||
from .notification_popup import NotificationPopup
|
||||
|
||||
|
||||
app = IgnisApp.get_default()
|
||||
|
||||
notifications = NotificationService.get_default()
|
||||
|
||||
|
||||
class Popup(Widget.Box):
|
||||
def __init__(self, notification: Notification):
|
||||
widget = NotificationWidget(notification)
|
||||
widget.css_classes = ["notification-popup"]
|
||||
self._inner = Widget.Revealer(transition_type="slide_left", child=widget)
|
||||
self._outer = Widget.Revealer(transition_type="slide_down", child=self._inner)
|
||||
super().__init__(child=[self._outer], halign="end")
|
||||
|
||||
notification.connect("dismissed", lambda x: self.destroy())
|
||||
|
||||
def destroy(self):
|
||||
def box_destroy():
|
||||
box: Widget.Box = self.get_parent() # type: ignore
|
||||
if not box:
|
||||
return
|
||||
|
||||
self.unparent()
|
||||
if len(notifications.popups) == 0:
|
||||
window: Widget.Window = box.get_parent() # type: ignore
|
||||
if not window:
|
||||
return
|
||||
window.visible = False
|
||||
else:
|
||||
change_window_input_region(box)
|
||||
|
||||
def outer_close():
|
||||
self._outer.reveal_child = False
|
||||
Utils.Timeout(self._outer.transition_duration, box_destroy)
|
||||
|
||||
self._inner.transition_type = "crossfade"
|
||||
self._inner.reveal_child = False
|
||||
Utils.Timeout(self._outer.transition_duration, outer_close)
|
||||
|
||||
|
||||
def on_notified(box: Widget.Box, notification: Notification, monitor: int) -> None:
|
||||
window: Widget.Window = app.get_window("ignis_CONTROL_CENTER") # type: ignore
|
||||
if window.visible and window.monitor == monitor:
|
||||
return
|
||||
|
||||
app.open_window(f"ignis_NOTIFICATION_POPUP_{monitor}")
|
||||
popup = Popup(notification)
|
||||
box.prepend(popup)
|
||||
popup._outer.reveal_child = True
|
||||
Utils.Timeout(popup._outer.transition_duration, reveal_popup, box, popup)
|
||||
|
||||
|
||||
def reveal_popup(box: Widget.Box, popup: Popup) -> None:
|
||||
popup._inner.set_reveal_child(True)
|
||||
change_window_input_region(box)
|
||||
|
||||
|
||||
def change_window_input_region(box: Widget.Box) -> None:
|
||||
def callback() -> None:
|
||||
width = box.get_width()
|
||||
height = box.get_height()
|
||||
window: Widget.Window = box.get_parent() # type: ignore
|
||||
|
||||
window.input_width = width
|
||||
window.input_height = height
|
||||
|
||||
Utils.Timeout(ms=50, target=callback)
|
||||
|
||||
|
||||
def notification_popup(monitor: int) -> Widget.Window:
|
||||
notifications_box = Widget.Box(
|
||||
vertical=True,
|
||||
valign="start",
|
||||
setup=lambda self: notifications.connect(
|
||||
"new_popup",
|
||||
lambda x, notification: on_notified(self, notification, monitor),
|
||||
),
|
||||
)
|
||||
|
||||
return Widget.Window(
|
||||
anchor=["right", "top", "bottom"],
|
||||
monitor=monitor,
|
||||
namespace=f"ignis_NOTIFICATION_POPUP_{monitor}",
|
||||
layer="top",
|
||||
child=notifications_box,
|
||||
visible=False,
|
||||
css_classes=["rec-unset"],
|
||||
style="min-width: 29rem;",
|
||||
)
|
||||
__all__ = ["NotificationPopup"]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,79 @@
|
||||
from ignis.widgets import Widget
|
||||
from ignis.app import IgnisApp
|
||||
from ignis.utils import Utils
|
||||
from ignis.services.notifications import Notification, NotificationService
|
||||
from ..shared_widgets import NotificationWidget
|
||||
|
||||
|
||||
app = IgnisApp.get_default()
|
||||
|
||||
notifications = NotificationService.get_default()
|
||||
|
||||
|
||||
class Popup(Widget.Box):
|
||||
def __init__(
|
||||
self, box: "PopupBox", window: "NotificationPopup", notification: Notification
|
||||
):
|
||||
self._box = box
|
||||
self._window = window
|
||||
|
||||
widget = NotificationWidget(notification)
|
||||
widget.css_classes = ["notification-popup"]
|
||||
self._inner = Widget.Revealer(transition_type="slide_left", child=widget)
|
||||
self._outer = Widget.Revealer(transition_type="slide_down", child=self._inner)
|
||||
super().__init__(child=[self._outer], halign="end")
|
||||
|
||||
notification.connect("dismissed", lambda x: self.destroy())
|
||||
|
||||
def destroy(self):
|
||||
def box_destroy():
|
||||
self.unparent()
|
||||
if len(notifications.popups) == 0:
|
||||
self._window.visible = False
|
||||
|
||||
def outer_close():
|
||||
self._outer.reveal_child = False
|
||||
Utils.Timeout(self._outer.transition_duration, box_destroy)
|
||||
|
||||
self._inner.transition_type = "crossfade"
|
||||
self._inner.reveal_child = False
|
||||
Utils.Timeout(self._outer.transition_duration, outer_close)
|
||||
|
||||
|
||||
class PopupBox(Widget.Box):
|
||||
def __init__(self, window: "NotificationPopup", monitor: int):
|
||||
self._window = window
|
||||
self._monitor = monitor
|
||||
|
||||
super().__init__(
|
||||
vertical=True,
|
||||
valign="start",
|
||||
setup=lambda self: notifications.connect(
|
||||
"new_popup",
|
||||
lambda x, notification: self.__on_notified(notification),
|
||||
),
|
||||
)
|
||||
|
||||
def __on_notified(self, notification: Notification) -> None:
|
||||
self._window.visible = True
|
||||
popup = Popup(box=self, window=self._window, notification=notification)
|
||||
self.prepend(popup)
|
||||
popup._outer.reveal_child = True
|
||||
Utils.Timeout(
|
||||
popup._outer.transition_duration, popup._inner.set_reveal_child, True
|
||||
)
|
||||
|
||||
|
||||
class NotificationPopup(Widget.Window):
|
||||
def __init__(self, monitor: int):
|
||||
super().__init__(
|
||||
anchor=["right", "top", "bottom"],
|
||||
monitor=monitor,
|
||||
namespace=f"ignis_NOTIFICATION_POPUP_{monitor}",
|
||||
layer="top",
|
||||
child=PopupBox(window=self, monitor=monitor),
|
||||
visible=False,
|
||||
dynamic_input_region=True,
|
||||
css_classes=["rec-unset"],
|
||||
style="min-width: 29rem;",
|
||||
)
|
||||
@@ -1,43 +1,3 @@
|
||||
from ignis.widgets import Widget
|
||||
from ignis.utils import Utils
|
||||
from ignis.services.audio import AudioService
|
||||
from .osd import OSD
|
||||
|
||||
audio = AudioService.get_default()
|
||||
|
||||
|
||||
class OSD(Widget.Window):
|
||||
def __init__(self):
|
||||
self._timer = None
|
||||
super().__init__(
|
||||
layer="overlay",
|
||||
anchor=["bottom"],
|
||||
namespace="ignis_OSD",
|
||||
visible=False,
|
||||
css_classes=["rec-unset"],
|
||||
child=Widget.Box(
|
||||
css_classes=["osd"],
|
||||
child=[
|
||||
Widget.Icon(
|
||||
pixel_size=26,
|
||||
style="margin-right: 0.5rem;",
|
||||
image=audio.speaker.bind("icon_name"),
|
||||
),
|
||||
Widget.Scale(
|
||||
value=audio.speaker.bind("volume"),
|
||||
css_classes=["material-slider"],
|
||||
sensitive=False,
|
||||
hexpand=True,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def set_property(self, property_name, value):
|
||||
if property_name == "visible":
|
||||
if self._timer:
|
||||
self._timer.cancel()
|
||||
self._timer = None
|
||||
|
||||
self._timer = Utils.Timeout(3000, self.set_visible, False)
|
||||
|
||||
super().set_property(property_name, value)
|
||||
__all__ = ["OSD"]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,38 @@
|
||||
from ignis.widgets import Widget
|
||||
from ignis.utils import Utils
|
||||
from ignis.services.audio import AudioService
|
||||
from ..shared_widgets import MaterialVolumeSlider
|
||||
|
||||
audio = AudioService.get_default()
|
||||
|
||||
|
||||
class OSD(Widget.Window):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
layer="overlay",
|
||||
anchor=["bottom"],
|
||||
namespace="ignis_OSD",
|
||||
visible=False,
|
||||
css_classes=["rec-unset"],
|
||||
child=Widget.Box(
|
||||
css_classes=["osd"],
|
||||
child=[
|
||||
Widget.Icon(
|
||||
pixel_size=26,
|
||||
style="margin-right: 0.5rem;",
|
||||
image=audio.speaker.bind("icon_name"),
|
||||
),
|
||||
MaterialVolumeSlider(stream=audio.speaker, sensitive=False),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
def set_property(self, property_name, value):
|
||||
if property_name == "visible":
|
||||
self.__update_visible()
|
||||
|
||||
super().set_property(property_name, value)
|
||||
|
||||
@Utils.debounce(3000)
|
||||
def __update_visible(self) -> None:
|
||||
super().set_property("visible", False)
|
||||
@@ -1,98 +1,3 @@
|
||||
from ignis.widgets import Widget
|
||||
from ignis.utils import Utils
|
||||
from ignis.app import IgnisApp
|
||||
from typing import Callable
|
||||
from .powermenu import Powermenu
|
||||
|
||||
app = IgnisApp.get_default()
|
||||
|
||||
|
||||
class PowermenuButton(Widget.Box):
|
||||
def __init__(self, label: str, icon_name: str, on_click: Callable) -> None:
|
||||
super().__init__(
|
||||
child=[
|
||||
Widget.Button(
|
||||
child=Widget.Icon(image=icon_name, pixel_size=36),
|
||||
on_click=on_click,
|
||||
css_classes=["powermenu-button", "unset"],
|
||||
),
|
||||
Widget.Label(label=label, css_classes=["powermenu-button-label"]),
|
||||
],
|
||||
vertical=True,
|
||||
css_classes=["powermenu-button-box"],
|
||||
)
|
||||
|
||||
|
||||
def poweroff(*args) -> None:
|
||||
Utils.exec_sh_async("poweroff")
|
||||
|
||||
|
||||
def reboot(*args) -> None:
|
||||
Utils.exec_sh_async("reboot")
|
||||
|
||||
|
||||
def suspend(*args) -> None:
|
||||
app.close_window("ignis_POWERMENU")
|
||||
Utils.exec_sh_async("systemctl suspend && hyprlock")
|
||||
|
||||
|
||||
def hypr_exit(*args) -> None:
|
||||
Utils.exec_sh_async("hyprctl dispatch exit 0")
|
||||
|
||||
|
||||
def powermenu():
|
||||
return Widget.Window(
|
||||
popup=True,
|
||||
kb_mode="on_demand",
|
||||
namespace="ignis_POWERMENU",
|
||||
exclusivity="ignore",
|
||||
anchor=["left", "right", "top", "bottom"],
|
||||
visible=False,
|
||||
child=Widget.Overlay(
|
||||
child=Widget.Button(
|
||||
vexpand=True,
|
||||
hexpand=True,
|
||||
can_focus=False,
|
||||
css_classes=["unset", "powermenu-overlay"],
|
||||
on_click=lambda x: app.close_window("ignis_POWERMENU"),
|
||||
),
|
||||
overlays=[
|
||||
Widget.Box(
|
||||
vertical=True,
|
||||
valign="center",
|
||||
halign="center",
|
||||
css_classes=["powermenu"],
|
||||
child=[
|
||||
Widget.Box(
|
||||
child=[
|
||||
PowermenuButton(
|
||||
label="Power off",
|
||||
icon_name="system-shutdown-symbolic",
|
||||
on_click=poweroff,
|
||||
),
|
||||
PowermenuButton(
|
||||
label="Reboot",
|
||||
icon_name="system-reboot-symbolic",
|
||||
on_click=reboot,
|
||||
),
|
||||
]
|
||||
),
|
||||
Widget.Box(
|
||||
child=[
|
||||
PowermenuButton(
|
||||
label="Suspend",
|
||||
icon_name="night-light-symbolic",
|
||||
on_click=suspend,
|
||||
),
|
||||
PowermenuButton(
|
||||
label="Sign out",
|
||||
icon_name="system-log-out-symbolic",
|
||||
on_click=hypr_exit,
|
||||
),
|
||||
]
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
css_classes=["unset"],
|
||||
)
|
||||
__all__ = ["Powermenu"]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,106 @@
|
||||
import asyncio
|
||||
from ignis.widgets import Widget
|
||||
from ignis.utils import Utils
|
||||
from ignis.app import IgnisApp
|
||||
from typing import Callable
|
||||
|
||||
app = IgnisApp.get_default()
|
||||
|
||||
def create_exec_task(cmd: str) -> None:
|
||||
asyncio.create_task(Utils.exec_sh_async(cmd))
|
||||
|
||||
class PowermenuButton(Widget.Box):
|
||||
def __init__(self, label: str, icon_name: str, on_click: Callable) -> None:
|
||||
super().__init__(
|
||||
child=[
|
||||
Widget.Button(
|
||||
child=Widget.Icon(image=icon_name, pixel_size=36),
|
||||
on_click=on_click,
|
||||
css_classes=["powermenu-button", "unset"],
|
||||
),
|
||||
Widget.Label(label=label, css_classes=["powermenu-button-label"]),
|
||||
],
|
||||
vertical=True,
|
||||
css_classes=["powermenu-button-box"],
|
||||
)
|
||||
|
||||
|
||||
class PowerOffButton(PowermenuButton):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
label="Power off",
|
||||
icon_name="system-shutdown-symbolic",
|
||||
on_click=lambda *args: create_exec_task("poweroff"),
|
||||
)
|
||||
|
||||
|
||||
class RebootButton(PowermenuButton):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
label="Reboot",
|
||||
icon_name="system-reboot-symbolic",
|
||||
on_click=lambda *args: create_exec_task("reboot"),
|
||||
)
|
||||
|
||||
|
||||
class SuspendButton(PowermenuButton):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
label="Suspend", icon_name="night-light-symbolic", on_click=self.__invoke
|
||||
)
|
||||
|
||||
def __invoke(self, *args) -> None:
|
||||
app.close_window("ignis_POWERMENU")
|
||||
create_exec_task("systemctl suspend && hyprlock")
|
||||
|
||||
|
||||
class HyprlandExitButton(PowermenuButton):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
label="Sign out",
|
||||
icon_name="system-log-out-symbolic",
|
||||
on_click=lambda *args: create_exec_task("hyprctl dispatch exit 0"),
|
||||
)
|
||||
|
||||
|
||||
class Powermenu(Widget.Window):
|
||||
def __init__(self):
|
||||
main_box = Widget.Box(
|
||||
vertical=True,
|
||||
valign="center",
|
||||
halign="center",
|
||||
css_classes=["powermenu"],
|
||||
child=[
|
||||
Widget.Box(
|
||||
child=[
|
||||
PowerOffButton(),
|
||||
RebootButton(),
|
||||
]
|
||||
),
|
||||
Widget.Box(
|
||||
child=[
|
||||
SuspendButton(),
|
||||
HyprlandExitButton(),
|
||||
]
|
||||
),
|
||||
],
|
||||
)
|
||||
super().__init__(
|
||||
popup=True,
|
||||
kb_mode="on_demand",
|
||||
namespace="ignis_POWERMENU",
|
||||
exclusivity="ignore",
|
||||
anchor=["left", "right", "top", "bottom"],
|
||||
visible=False,
|
||||
child=Widget.Overlay(
|
||||
child=Widget.Button(
|
||||
vexpand=True,
|
||||
hexpand=True,
|
||||
can_focus=False,
|
||||
css_classes=["unset", "powermenu-overlay"],
|
||||
on_click=lambda x: app.close_window("ignis_POWERMENU"),
|
||||
),
|
||||
overlays=[main_box],
|
||||
),
|
||||
css_classes=["unset"],
|
||||
)
|
||||
@@ -1,83 +1,5 @@
|
||||
from ignis.widgets import Widget
|
||||
from ignis.gobject import IgnisGObject
|
||||
from gi.repository import GObject # type: ignore
|
||||
from .notifications import notifications_entry
|
||||
from .about import about_entry
|
||||
from .appearance import appearance_entry
|
||||
from .recorder import recorder_entry
|
||||
from .user import user_entry
|
||||
from .elements import SettingsPage
|
||||
from ignis.app import IgnisApp
|
||||
from ignis.exceptions import WindowNotFoundError
|
||||
from options import settings_last_page
|
||||
from .settings import Settings
|
||||
|
||||
app = IgnisApp.get_default()
|
||||
|
||||
|
||||
class ActivePage(IgnisGObject):
|
||||
def __init__(self, name: str | None, page: SettingsPage | None):
|
||||
super().__init__()
|
||||
self._name = name
|
||||
self._page = page
|
||||
|
||||
@GObject.Property
|
||||
def name(self) -> str | None:
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, value: str | None) -> None:
|
||||
self._name = value
|
||||
|
||||
@GObject.Property
|
||||
def page(self) -> SettingsPage | None:
|
||||
return self._page
|
||||
|
||||
@page.setter
|
||||
def page(self, value: SettingsPage | None) -> None:
|
||||
self._page = value
|
||||
|
||||
|
||||
def settings_widget():
|
||||
active_page = ActivePage(name="Settings", page=None)
|
||||
content = Widget.Box(
|
||||
hexpand=True,
|
||||
vexpand=True,
|
||||
child=active_page.bind("page", transform=lambda value: [value]),
|
||||
)
|
||||
listbox = Widget.ListBox(
|
||||
rows=[
|
||||
notifications_entry(active_page),
|
||||
recorder_entry(active_page),
|
||||
appearance_entry(active_page),
|
||||
user_entry(active_page),
|
||||
about_entry(active_page),
|
||||
],
|
||||
)
|
||||
|
||||
listbox.select_row(listbox.rows[settings_last_page.value])
|
||||
|
||||
navigation_sidebar = Widget.Box(
|
||||
vertical=True,
|
||||
css_classes=["settings-sidebar"],
|
||||
child=[
|
||||
Widget.Label(
|
||||
label="Settings", halign="start", css_classes=["settings-sidebar-label"]
|
||||
),
|
||||
listbox,
|
||||
],
|
||||
)
|
||||
|
||||
return Widget.Box(child=[navigation_sidebar, content])
|
||||
|
||||
|
||||
def settings_window():
|
||||
try:
|
||||
app.get_window("ignis_SETTINGS")
|
||||
except WindowNotFoundError:
|
||||
return Widget.RegularWindow(
|
||||
default_width=900,
|
||||
default_height=600,
|
||||
resizable=False,
|
||||
child=settings_widget(),
|
||||
namespace="ignis_SETTINGS",
|
||||
)
|
||||
__all__ = [
|
||||
"Settings",
|
||||
]
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
from ignis.variable import Variable
|
||||
from .elements import SettingsPage
|
||||
|
||||
fallback_page = SettingsPage(name="Settings")
|
||||
active_page = Variable(value=fallback_page)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,5 @@
|
||||
from ignis.widgets import Widget
|
||||
from .page import SettingsPage
|
||||
from options import settings_last_page
|
||||
|
||||
|
||||
class SettingsEntry(Widget.ListBoxRow):
|
||||
@@ -8,15 +7,10 @@ class SettingsEntry(Widget.ListBoxRow):
|
||||
self,
|
||||
icon: str,
|
||||
label: str,
|
||||
active_page,
|
||||
page: SettingsPage,
|
||||
**kwargs,
|
||||
):
|
||||
def callback(x):
|
||||
active_page.page = page
|
||||
active_page.name = label
|
||||
if self in self.parent.rows:
|
||||
settings_last_page.set_value(self.parent.rows.index(self))
|
||||
from ..active_page import active_page # avoid a circular import
|
||||
|
||||
super().__init__(
|
||||
child=Widget.Box(
|
||||
@@ -26,6 +20,6 @@ class SettingsEntry(Widget.ListBoxRow):
|
||||
],
|
||||
),
|
||||
css_classes=["settings-sidebar-entry"],
|
||||
on_activate=callback,
|
||||
on_activate=lambda x: active_page.set_value(page),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
from typing import List
|
||||
from ignis.widgets import Widget
|
||||
from .row import SettingsRow
|
||||
from ignis.base_widget import BaseWidget
|
||||
@@ -6,7 +5,7 @@ from ignis.base_widget import BaseWidget
|
||||
|
||||
class SettingsGroup(Widget.Box):
|
||||
def __init__(
|
||||
self, name: str | None, rows: List[SettingsRow | BaseWidget] = [], **kwargs
|
||||
self, name: str | None, rows: list[SettingsRow | BaseWidget] = [], **kwargs
|
||||
):
|
||||
super().__init__(
|
||||
vertical=True,
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
from ignis.widgets import Widget
|
||||
from typing import List
|
||||
from .group import SettingsGroup
|
||||
from ignis.base_widget import BaseWidget
|
||||
|
||||
|
||||
class SettingsPage(Widget.Scroll):
|
||||
def __init__(self, name: str, groups: List[SettingsGroup | BaseWidget] = []):
|
||||
def __init__(self, name: str, groups: list[SettingsGroup | BaseWidget] = []):
|
||||
super().__init__(
|
||||
hexpand=True,
|
||||
vexpand=True,
|
||||
|
||||
@@ -2,7 +2,12 @@ from ignis.widgets import Widget
|
||||
|
||||
|
||||
class SettingsRow(Widget.ListBoxRow):
|
||||
def __init__(self, label: str | None = None, sublabel: str | None = None, **kwargs):
|
||||
def __init__(
|
||||
self,
|
||||
label: str | None = None,
|
||||
sublabel: str | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
css_classes=["settings-row"],
|
||||
child=Widget.Box(
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
from .about import AboutEntry
|
||||
from .appearance import AppearanceEntry
|
||||
from .notifications import NotificationsEntry
|
||||
from .recorder import RecorderEntry
|
||||
from .user import UserEntry
|
||||
|
||||
__all__ = [
|
||||
"AboutEntry",
|
||||
"AppearanceEntry",
|
||||
"NotificationsEntry",
|
||||
"RecorderEntry",
|
||||
"UserEntry",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user