diff --git a/assets/svg/audio-volume.svg b/assets/svg/audio-volume.svg deleted file mode 100644 index e4df579..0000000 --- a/assets/svg/audio-volume.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/config/assets/icons/brightness.svg b/config/assets/icons/brightness.svg index 564b35d..bcc61ef 100755 --- a/config/assets/icons/brightness.svg +++ b/config/assets/icons/brightness.svg @@ -1,141 +1,6 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - + + + + + diff --git a/config/assets/icons/brightness/brightness-0.svg b/config/assets/icons/brightness/brightness-0.svg new file mode 100755 index 0000000..d6065c7 --- /dev/null +++ b/config/assets/icons/brightness/brightness-0.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/config/assets/icons/brightness/brightness-1.svg b/config/assets/icons/brightness/brightness-1.svg new file mode 100755 index 0000000..e5b4101 --- /dev/null +++ b/config/assets/icons/brightness/brightness-1.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/config/assets/icons/brightness/brightness-2.svg b/config/assets/icons/brightness/brightness-2.svg new file mode 100755 index 0000000..df7cfba --- /dev/null +++ b/config/assets/icons/brightness/brightness-2.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/config/assets/icons/brightness/brightness-3.svg b/config/assets/icons/brightness/brightness-3.svg new file mode 100755 index 0000000..c3b390b --- /dev/null +++ b/config/assets/icons/brightness/brightness-3.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/main.css b/main.css index 159db87..9be58bb 100644 --- a/main.css +++ b/main.css @@ -5,6 +5,7 @@ @import url("./styles/switcher.css"); @import url("./styles/launcher.css"); @import url("./styles/osd.css"); +@import url("./styles/about.css"); * { all: unset; diff --git a/main.py b/main.py index b2d7cd1..72602a4 100644 --- a/main.py +++ b/main.py @@ -7,6 +7,7 @@ from fabric.utils import get_relative_path, monitor_file from modules.dock import Dock from modules.launcher.main import Launcher from modules.osd import OSD +from modules.about import About # from modules.corners import Corners from modules.panel.main import Panel @@ -28,6 +29,7 @@ if __name__ == "__main__": # Load configuration from config.data import load_config + About().toggle(None) config = load_config() panel = Panel() diff --git a/modules/about.py b/modules/about.py new file mode 100644 index 0000000..ad78bda --- /dev/null +++ b/modules/about.py @@ -0,0 +1,178 @@ +import subprocess +import re +import gi # type: ignore + +from fabric.widgets.label import Label +from fabric.widgets.button import Button +from fabric.widgets.box import Box +from utils.wayland import WaylandWindow as Window +from fabric.utils.helpers import exec_shell_command_async, get_relative_path +from gi.repository import Gtk, GdkPixbuf # type: ignore + + +def read_dmi(field): + try: + with open(f"/sys/class/dmi/id/{field}") as f: + return f.read().strip() + except Exception: + return "Unknown" + + +def get_gpu_name(): + try: + output = ( + subprocess.check_output( + "lspci -nn | grep -i 'VGA compatible controller'", shell=True, text=True + ) + .strip() + .split("\n") + ) + + def clean(line): + matches = re.findall(r"\[(.*?)\]", line) + if len(matches) >= 2: + return matches[1].strip() + desc = line.split(":", 2)[-1] + return desc.replace("Corporation", "").strip() + + for line in output: + if any(vendor in line.lower() for vendor in ["nvidia", "amd", "radeon"]): + return clean(line) + + if output: + return clean(output[0]) + + return "Unknown GPU" + except Exception: + return "Unknown GPU" + + +class About(Gtk.Window): + def __init__(self): + super().__init__(title="About Menu") + self.set_default_size(300, 550) + self.set_size_request(300, 500) + self.set_resizable(False) + self.set_wmclass("modus-about-menu", "modus-about-menu") + self.set_name("about-menu") + self.set_visible(False) + + main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) + main_box.set_margin_top(10) + main_box.set_margin_bottom(20) + main_box.set_margin_start(20) + main_box.set_margin_end(20) + + # Logo + logo_box = Gtk.Box(halign=Gtk.Align.CENTER, valign=Gtk.Align.CENTER) + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( + get_relative_path("../config/assets/icons/imac.svg"), + 158, + 108, + preserve_aspect_ratio=True, + ) + logo = Gtk.Image.new_from_pixbuf(pixbuf) + logo_box.pack_start(logo, False, False, 0) + logo_box.set_margin_top(60) + + # Product Name & Vendor + name_label = Gtk.Label() + name_label.set_margin_top(30) + name_label.set_markup( + f"{read_dmi('product_name')}" + ) + + vendor_label = Gtk.Label(label=read_dmi("sys_vendor")) + vendor_label.set_name("vendor-label") + vendor_label.set_halign(Gtk.Align.CENTER) + + # Info Grid + info_grid = Gtk.Grid() + info_grid.set_row_spacing(6) + info_grid.set_column_spacing(10) + info_grid.set_valign(Gtk.Align.CENTER) + info_grid.set_halign(Gtk.Align.FILL) + + def make_label(text, align_end=False, name=None): + label = Gtk.Label(label=text) + label.set_halign(Gtk.Align.END if align_end else Gtk.Align.START) + if name: + label.set_name(name) + return label + + # Info values + labels = [ + ( + "Kernel", + subprocess.run( + "uname -r", shell=True, capture_output=True, text=True + ).stdout.strip(), + ), + ( + "CPU", + subprocess.run( + "lscpu | grep 'Model name:' | cut -d ':' -f2-", + shell=True, + capture_output=True, + text=True, + ).stdout.strip(), + ), + ( + "Memory", + subprocess.run( + "free -h --giga | grep Mem | tr -s ' ' | cut -d ' ' -f 2", + shell=True, + capture_output=True, + text=True, + ).stdout.strip(), + ), + ("GPU", get_gpu_name()), + ( + "Uptime", + subprocess.run( + "uptime -p", shell=True, capture_output=True, text=True + ).stdout.strip(), + ), + ] + + for i, (title, value) in enumerate(labels): + title_label = make_label(title, align_end=True) + value_label = make_label(value, align_end=False, name="info-label") + info_grid.attach(title_label, 0, i, 1, 1) + info_grid.attach(value_label, 1, i, 1, 1) + + # Button + button_box = Gtk.Box(halign=Gtk.Align.CENTER) + button_box.set_margin_top(20) + more_info_button = Gtk.Button(label="More Info...", name="more-info-button") + more_info_button.connect("clicked", self.open_more_info) + button_box.pack_start(more_info_button, False, False, 0) + + # Info Footer + info = Gtk.Label( + label="™ and © 2025 Linux Inc.\nAll rights reserved.\n\n", + justify=Gtk.Justification.CENTER, + halign=Gtk.Align.CENTER, + ) + info.set_name("info-label") + info.set_margin_top(10) + + # Layout Order + main_box.pack_start(logo_box, False, False, 0) + main_box.pack_start(name_label, False, False, 0) + main_box.pack_start(vendor_label, False, False, 0) + main_box.pack_start(info_grid, False, False, 0) + main_box.pack_start(button_box, False, False, 0) + main_box.pack_start(info, False, False, 0) + + self.add(main_box) + + def open_more_info(self, button): + # TODO: Implement the logic to open more information + pass + + def toggle(self, b): + if self.get_visible(): + self.hide() + else: + self.show_all() diff --git a/modules/osd.py b/modules/osd.py index 66748a9..bf35df2 100644 --- a/modules/osd.py +++ b/modules/osd.py @@ -39,7 +39,7 @@ class AnimatedScale(Scale): class BrightnessOSDContainer(Box): def __init__(self, **kwargs): - super().__init__(**kwargs, orientation="v", spacing=12, name="osd") + super().__init__(**kwargs, orientation="v", spacing=3, name="osd") self.brightness_service = Brightness.get_initial() self.scale = AnimatedScale( marks=(ScaleMark(value=i) for i in range(0, 101, 10)), @@ -51,7 +51,7 @@ class BrightnessOSDContainer(Box): ) self.osd_window_image = Svg( get_relative_path("../config/assets/icons/brightness.svg"), - size=(64, 150), + size=(84, 150), name="osd-image", h_align="center", v_align="center", @@ -72,8 +72,17 @@ class BrightnessOSDContainer(Box): if current_brightness != 0: self.scale.animate_value(normalized_brightness) + def get_svg(self, value): + b_level = 0 if value == 0 else min(int(math.ceil(value / 33)), 3) + return b_level + def on_brightness_changed(self, _sender: any, value: float, *_args) -> None: normalized_brightness = self._normalize_brightness(value) + self.osd_window_image.set_from_file( + get_relative_path( + f"../config/assets/icons/brightness/brightness-{self.get_svg(normalized_brightness)}.svg" + ) + ) self.scale.animate_value(normalized_brightness) def _normalize_brightness(self, brightness: float) -> float: diff --git a/styles/about.css b/styles/about.css new file mode 100644 index 0000000..b2582c9 --- /dev/null +++ b/styles/about.css @@ -0,0 +1,77 @@ +#about-menu { + background-color: alpha(#111, 0.6); + box-shadow: inset 0 -0.5px 0 .5px alpha(#555, 0.7), inset 0 0.5px 0 .5px alpha(#777, 0.7); + border: 1px solid alpha(#111, 0.3); + border-top: none; + border-radius: 1.25rem; + padding: 2rem; +} + +#about-options { + background-color: alpha(#333, 0.9); + border-radius: 15px; + padding: 2rem; +} + +#about-logo-box { + margin-top: 2rem; + margin-bottom: 2rem; +} + +#about-button-box { + padding: 1rem; +} +#vendor-label{ + color: #888888; + margin-top : -1rem; +} + +#info-label{ + color: #888888; +} +#about-info-title-box { + padding-left: 4rem; +} + +#about-name-label { + font-size: 24px; + margin: 0px; + font-weight: 600; +} + +#about-date-label { + font-size: 11px; + margin: 0px; + color: #ddd; + font-weight: 400; + margin-bottom: 2rem; +} + +#about-chip-title-label, +#about-so-title-label, +#about-mem-title-label { + font-size: 12px; + margin: 0px; + margin-right: 10px; + font-weight: 500; +} + +#more-info-button { + background-color: #2369ff; + border-radius: 5px; + padding: 0px 10px; + margin: 1px 2px; + transition: all 200ms ease-in-out; +} + +#more-info-button:hover { + background-color: rgb(72, 130, 255); +} + +#more-info-button label { + font-size: 12px; + margin: 0px; + font-weight: 400; + color: #ddd; +} + diff --git a/styles/osd.css b/styles/osd.css index a831755..d306b45 100644 --- a/styles/osd.css +++ b/styles/osd.css @@ -12,7 +12,7 @@ #osd trough { background: var(--surface); - min-height: 30px; + min-height: 15px; margin-right: 4px; transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275); border-radius: 16px;