From e328e3af7a0abd5abdbd66199e2efe57c7c6c7aa Mon Sep 17 00:00:00 2001 From: Vladislav Nepogodin Date: Sun, 3 Jul 2022 03:47:20 +0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=B7=20add=20new=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 91 ++- Cargo.toml | 6 +- data/application_utility/advanced.json | 702 ++++++++++++++++++++ data/application_utility/default.json | 874 +++++++++++++++++++++++++ meson.build | 2 +- src/alpm_helper.rs | 103 +++ src/application_browser.rs | 426 ++++++++++++ src/main.rs | 281 +------- src/pages.rs | 388 +++++++++++ src/utils.rs | 11 + ui/cachyos-hello.glade | 19 +- 11 files changed, 2604 insertions(+), 299 deletions(-) create mode 100644 data/application_utility/advanced.json create mode 100644 data/application_utility/default.json create mode 100644 src/alpm_helper.rs create mode 100644 src/application_browser.rs create mode 100644 src/pages.rs diff --git a/Cargo.lock b/Cargo.lock index e51f8a0..e3a3291 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,10 +12,39 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.57" +name = "alpm" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "bedac9e074b9368777268cff76c02d8b26d3e567642a52379aadd5c5e204e120" +dependencies = [ + "alpm-sys", + "bitflags", +] + +[[package]] +name = "alpm-sys" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee1afe3484685ecedf03aefd7904428befed7815687e48aa4b21b3417eeecf0" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "alpm-utils" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09329cd59e48e140faceb66ec714d8b5fe28a414327b381ae9a8af47ab29a700" +dependencies = [ + "alpm", + "pacmanconf", +] + +[[package]] +name = "anyhow" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" [[package]] name = "atk" @@ -61,8 +90,10 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "cachyos-hello" -version = "0.7.0" +version = "0.8.0" dependencies = [ + "alpm", + "alpm-utils", "gdk", "gdk-pixbuf", "gettext-rs", @@ -70,6 +101,7 @@ dependencies = [ "glib", "gtk", "once_cell", + "pacmanconf", "serde", "serde_json", "subprocess", @@ -77,9 +109,9 @@ dependencies = [ [[package]] name = "cairo-rs" -version = "0.15.11" +version = "0.15.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62be3562254e90c1c6050a72aa638f6315593e98c5cdaba9017cedbabf0a5dee" +checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" dependencies = [ "bitflags", "cairo-sys-rs", @@ -114,6 +146,12 @@ dependencies = [ "smallvec", ] +[[package]] +name = "cini" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "895f4cd6403d7bd85e13ff3ec635d58183e5e708e7bd70430a91d661b04548e2" + [[package]] name = "field-offset" version = "0.3.4" @@ -491,6 +529,15 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +[[package]] +name = "pacmanconf" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faba895e4237d5dfd3459a4c0ef807b3510e088d28e7f5fd71a72962af84fef4" +dependencies = [ + "cini", +] + [[package]] name = "pango" version = "0.15.10" @@ -579,18 +626,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ "proc-macro2", ] @@ -647,18 +694,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" dependencies = [ "proc-macro2", "quote", @@ -667,9 +714,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" dependencies = [ "itoa", "ryu", @@ -684,9 +731,9 @@ checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "smallvec" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "subprocess" @@ -700,9 +747,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", @@ -765,9 +812,9 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "version-compare" diff --git a/Cargo.toml b/Cargo.toml index 8f17079..2ec939e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,15 @@ [package] name = "cachyos-hello" -version = "0.7.0" +version = "0.8.0" authors = ["Vladislav Nepogodin "] license = "GPLv3" edition = "2021" [dependencies] +alpm = "2.2.1" +alpm-utils = "1.1.2" +pacmanconf = "1.0.0" + subprocess = "0.2.9" once_cell = "1.12.0" gettext-rs = { version = "0.7", features = ["gettext-system"] } diff --git a/data/application_utility/advanced.json b/data/application_utility/advanced.json new file mode 100644 index 0000000..1e27270 --- /dev/null +++ b/data/application_utility/advanced.json @@ -0,0 +1,702 @@ +[ + { + "name": "Browsers", + "icon": "browser", + "description": "Web browsing and communication", + "apps": [ + { + "name": "Chromium", + "icon": "chromium", + "description": "Open Sourced Chrome Browser", + "pkg": "chromium", + "extra": [] + }, + { + "name": "Epiphany", + "icon": "epiphany", + "description": "Open source Web Browser", + "pkg": "epiphany", + "extra": [] + }, + { + "name": "Falkon", + "icon": "falkon", + "description": "Qt based Web Browser", + "pkg": "falkon", + "extra": [] + }, + { + "name": "Firefox", + "icon": "mozilla-firefox", + "description": "Mozilla Web Browser", + "pkg": "firefox", + "extra": [] + }, + { + "name": "Konqueror", + "icon": "konqueror", + "description": "Konqueror Web Browser", + "pkg": "konqueror", + "extra": [] + }, + { + "name": "Midori", + "icon": "midori", + "description": "Lightweight Webbrowser", + "pkg": "midori", + "extra": [] + }, + { + "name": "Netsurf", + "icon": "netsurf", + "description": "Light and Fast Web Browser", + "pkg": "netsurf", + "extra": [] + }, + { + "name": "Opera", + "icon": "opera", + "description": "Fast and secure webbrowser", + "pkg": "opera", + "extra": [] + } + ] + }, + { + "name": "E-mail", + "icon": "mail-client", + "description": "E-mail, Calendar, Tasks", + "apps": [ + { + "name": "Claws Mail", + "icon": "claws-mail", + "description": "Lightweight and fast GTK+ based Mail Client", + "pkg": "claws-mail", + "extra": [] + }, + { + "name": "Evolution", + "icon": "evolution", + "description": "Manage your email, contacts and schedule", + "pkg": "evolution", + "extra": [] + }, + { + "name": "Geary", + "icon": "geary", + "description": "Send and receive mail", + "pkg": "geary", + "extra": [] + }, + { + "name": "KMail", + "icon": "kmail", + "description": "KDE E-mail client", + "pkg": "kmail", + "extra": [] + }, + { + "name": "Sylpheed", + "icon": "sylpheed", + "description": "E-mail client", + "pkg": "sylpheed", + "extra": [] + }, + { + "name": "Thunderbird", + "icon": "thunderbird", + "description": "Send and receive mail, contacts and schedule", + "pkg": "thunderbird", + "extra": [] + } + ] + }, + { + "name": "Office Suites", + "icon": "applications-office", + "description": "Office suites like MS Office", + "apps": [ + { + "name": "Calligra Office", + "icon": "calligrawords", + "description": "Qt Based Office Suite", + "pkg": "calligra", + "extra": [] + }, + { + "name": "Free Office", + "icon": "freeoffice-planmaker", + "description": "Free Office suite compatible with Microsoft Office", + "pkg": "freeoffice", + "extra": [] + }, + { + "name": "Libre Office (Fresh)", + "icon": "libreoffice-main", + "description": "Open Source Office Application (Lastest)", + "pkg": "libreoffice-fresh", + "extra": [] + }, + { + "name": "Libre Office (Still)", + "icon": "libreoffice-main", + "description": "Open Source Office Application (Stable)", + "pkg": "libreoffice-still", + "extra": [] + }, + { + "name": "MS Office Online", + "icon": "ms-word", + "description": "Microsoft Office Online", + "pkg": "microsoft-office-web-jak", + "extra": [] + } + ] + }, + { + "name": "Office Apps", + "icon": "gnumeric", + "description": "Stand alone applications", + "apps": [ + { + "name": "Abiword", + "icon": "abiword", + "description": "Compose, Edit and view documents", + "pkg": "abiword", + "extra": [] + }, + { + "name": "Gnumeric", + "icon": "gnumeric", + "description": "A High Precision Spreadsheet Program", + "pkg": "gnumeric", + "extra": [] + } + ] + }, + { + "name": "Printing", + "icon": "printer", + "description": "Support for printing CUPS, HPLIP", + "apps": [ + { + "name": "Manjaro Printing", + "icon": "printer", + "description": "Printing Meta Package", + "pkg": "manjaro-printer", + "extra": [] + } + ] + }, + { + "name": "PDF", + "icon": "pdfeditor", + "description": "PDF applications applications", + "apps": [ + { + "name": "Epdfview", + "icon": "qpdfview", + "description": "Lightweight PDF document viewer", + "pkg": "epdfview", + "extra": [] + }, + { + "name": "Evince", + "icon": "evince", + "description": "View multi page documents", + "pkg": "evince", + "extra": [] + }, + { + "name": "Okular", + "icon": "okular", + "description": "Document Viewer", + "pkg": "Okular", + "extra": [] + }, + { + "name": "PDFMod", + "icon": "pdfmod", + "description": "Remove, extract and rotate pages in PDF Documents", + "pkg": "pdfmod", + "extra": [] + }, + { + "name": "Qpdfview", + "icon": "qpdfview", + "description": "Tabbed document viewer", + "pkg": "qpdfview", + "extra": [] + } + ] + }, + { + "name": "E-Book", + "icon": "calibre", + "description": "E-book library apps", + "apps": [ + { + "name": "Calibre", + "icon": "calibre-viewer", + "description": "The one stop solution to your e-book needs", + "pkg": "calibre", + "extra": [] + }, + { + "name": "FBReader", + "icon": "fbreader", + "description": "FBReader E-Book Reader", + "pkg": "fbreader", + "extra": [] + } + ] + }, + { + "name": "Backup", + "icon": "deja-dup", + "description": "Backup utilites", + "apps": [ + { + "name": "Deja Dup", + "icon": "deja-dup", + "description": "Keep your important documents safe from disater", + "pkg": "deja-dup", + "extra": [] + }, + { + "name": "Grsync", + "icon": "grsync", + "description": "Synchronize files and folders", + "pkg": "grsync", + "extra": [] + }, + { + "name": "Timeshift", + "icon": "timeshift", + "description": "A system restore utility for Linux", + "pkg": "timeshift", + "extra": [] + } + ] + }, + { + "name": "Text Editors", + "icon": "text-editor", + "description": "Various editors for text or code", + "apps": [ + { + "name": "Atom", + "icon": "atom", + "description": "A hackable text editor for the 21st Century", + "pkg": "atom", + "extra": [] + }, + { + "name": "Geany", + "icon": "geany", + "description": "A fast and lightweight IDE using GTK+", + "pkg": "geany", + "extra": [] + }, + { + "name": "Mousepad", + "icon": "mousepad", + "description": "Simple Text Editor", + "pkg": "mousepad", + "extra": [] + }, + { + "name": "Visual Studio Code OSS", + "icon": "visual-studio-code", + "description": "Microsoft Code Editor", + "pkg": "code", + "extra": [] + }, + { + "name": "Xed", + "icon": "xed", + "description": "A small and lightweight text editor. X Apps Project", + "pkg": "xed", + "extra": [] + } + ] + }, + { + "name": "System Tools", + "icon": "disk-utility", + "description": "System utilities", + "apps": [ + { + "name": "Etcher", + "icon": "etcher-electron", + "description": "Graphical tool to write ISO to USB", + "pkg": "etcher", + "extra": [] + }, + { + "name": "Gnome Disks", + "icon": "gnome-disks", + "description": "Disk management system for Gnome", + "pkg": "gnome-disk-utility", + "extra": [] + }, + { + "name": "Gparted", + "icon": "gparted", + "description": "Create, reorganize, and delete partitions", + "pkg": "gparted", + "extra": [] + }, + { + "name": "IsoUSB", + "icon": "usb-creator", + "description": "A graphical tool to copy a hybrid ISO onto a USB key.", + "pkg": "isousb", + "extra": [] + }, + { + "name": "Mintstick", + "icon": "mintstick", + "description": "Format or wirte imges to usb sticks (Linux Mint tool).", + "pkg": "mintstick", + "extra": [] + }, + { + "name": "Pamac", + "icon": "pamac-updater", + "description": "Update your System, Add/Remove Software from repo and AUR", + "pkg": "pamac", + "extra": [] + }, + { + "name": "Yay", + "icon": "terminal", + "description": "CLI AUR helper", + "pkg": "yay", + "extra": [] + } + ] + }, + { + "name": "Security", + "icon": "security-high", + "description": "Security oriented utilities", + "apps": [ + { + "name": "KeePassX", + "icon": "keepassx", + "description": "Cross Platform Password Manager", + "pkg": "keepassx", + "extra": [] + }, + { + "name": "SeaHorse", + "icon": "seahorse", + "description": "Manage your passwords and encryption keys", + "pkg": "seahorse", + "extra": [] + }, + { + "name": "VeraCrypt", + "icon": "veracrypt", + "description": "Disk encryption with strong security based on TrueCrypt", + "pkg": "veracrypt", + "extra": [] + } + ] + }, + { + "name": "Virtual Computing", + "icon": "virt-manager", + "description": "Virtual Machine applications", + "apps": [ + { + "name": "VirtualBox", + "icon": "virtualbox", + "description": "Run several virtual systems on a single host computer", + "pkg": "calibre", + "extra": [] + }, + { + "name": "Gnome Boxes", + "icon": "gnome-boxes", + "description": "Simple remote and virtual machines", + "pkg": "gnome-boxes", + "extra": [] + } + ] + }, + { + "name": "Chat", + "icon": "internet-chat", + "description": "Online messaging and chat", + "apps": [ + { + "name": "HexChat", + "icon": "hexchat", + "description": "Graphic IRC Client", + "pkg": "hexchat", + "extra": [] + }, + { + "name": "Pidgin Messenger", + "icon": "pidgin", + "description": "Instant messaging Client", + "pkg": "pidgin", + "extra": [] + } + ] + }, + { + "name": "File Sharing", + "icon": "transmission", + "description": "FTP and Torrent apps", + "apps": [ + { + "name": "Filezilla", + "icon": "filezilla", + "description": "Graphical FTP/FTPS/SFTP browser", + "pkg": "filezilla", + "extra": [] + }, + { + "name": "qBittorrent", + "icon": "qbittorrent", + "description": "A Qt based Bittorrent Client", + "pkg": "qbittorrent", + "extra": [] + }, + { + "name": "Transmission GTK", + "icon": "transmission", + "description": "GTK based Bittorrent Client", + "pkg": "transmission-gtk", + "extra": [] + }, + { + "name": "QTransmission", + "icon": "transmission", + "description": "QT based Bittorrent Client", + "pkg": "transmission-qt", + "extra": [] + } + ] + }, + { + "name": "Graphics Creating", + "icon": "applications-accessories", + "description": "Creating and editing graphics", + "apps": [ + { + "name": "Blender", + "icon": "blender", + "description": "3D modeling and animation", + "pkg": "blender", + "extra": [] + }, + { + "name": "GIMP", + "icon": "gimp", + "description": "Create images and edit photographs", + "pkg": "gimp", + "extra": [] + }, + { + "name": "Inkscape", + "icon": "inkscape", + "description": "Vector Graphics Editor", + "pkg": "inkscape", + "extra": [] + }, + { + "name": "Krita", + "icon": "krita", + "description": "Digital Painting Creative Freedom", + "pkg": "krita", + "extra": [] + }, + { + "name": "Pinta", + "icon": "pinta", + "description": "Easy create and edit images", + "pkg": "pinta", + "extra": [] + }, + { + "name": "Tux Paint", + "icon": "tuxpaint", + "description": "Drawing program for children", + "pkg": "tuxpaint", + "extra": [] + } + ] + }, + { + "name": "Graphics Organizing", + "icon": "applications-graphics", + "description": "Viewers and organizers", + "apps": [ + { + "name": "Gpicview", + "icon": "gpicview", + "description": "Lightweight Image Viewer", + "pkg": "gpicview", + "extra": [] + }, + { + "name": "gThumb", + "icon": "gthumb", + "description": "View and organize your images", + "pkg": "gthumb", + "extra": [] + }, + { + "name": "Gwenview", + "icon": "gwenview", + "description": "Image Viewer", + "pkg": "gwenview", + "extra": [] + }, + { + "name": "Ristretto", + "icon": "ristretto", + "description": "Free and lightweight image viewer", + "pkg": "ristretto", + "extra": [] + }, + { + "name": "Shotwell", + "icon": "shotwell", + "description": "Popular Photo Manager", + "pkg": "shotwell", + "extra": [] + }, + { + "name": "Viewnior", + "icon": "viewnior", + "description": "GTK based Elegant Image Viewer", + "pkg": "viewnior", + "extra": [] + } + ] + }, + { + "name": "Video/Movie", + "icon": "video-player", + "description": "Organize and play videos and movies", + "apps": [ + { + "name": "Kodi", + "icon": "kodi", + "description": "Manage and view your media", + "pkg": "kodi", + "extra": [] + }, + { + "name": "Parole", + "icon": "parole", + "description": "Modern and simple media player", + "pkg": "parole", + "extra": [] + }, + { + "name": "SM Player", + "icon": "smplayer", + "description": "A great MPlayer front end", + "pkg": "smplayer", + "extra": [] + }, + { + "name": "Totem", + "icon": "totem", + "description": "Play movies", + "pkg": "totem", + "extra": [] + }, + { + "name": "VLC", + "icon": "vlc", + "description": "VLC media player, the openxource multimedia player", + "pkg": "vlc", + "extra": [] + } + ] + }, + { + "name": "Audio", + "icon": "musicbrainz", + "description": "Audio players", + "apps": [ + { + "name": "Audacious", + "icon": "audacious", + "description": "Listen to music", + "pkg": "audacious", + "extra": [] + }, + { + "name": "Clementine", + "icon": "clementine", + "description": "Play music files and internet radio", + "pkg": "clementine", + "extra": [] + }, + { + "name": "DeadBeeF", + "icon": "deadbeef", + "description": "Listen to music", + "pkg": "deadbeef", + "extra": [] + }, + { + "name": "Lollypop", + "icon": "lollypop", + "description": "Play and organize your music collection", + "pkg": "deadbeef", + "extra": [] + }, + { + "name": "Rhythmbox", + "icon": "rhythmbox", + "description": "Gnome music playing application", + "pkg": "rhythmbox", + "extra": [] + } + ] + }, + { + "name": "Media recording/editing", + "icon": "kdenlive", + "description": "Audio and Video editing", + "apps": [ + { + "name": "Audacity", + "icon": "audacity", + "description": "Record and Edit Audio files", + "pkg": "audacity", + "extra": [] + }, + { + "name": "Kdenlive", + "icon": "kdenlive", + "description": "Video Editor", + "pkg": "kdenlive", + "extra": [] + }, + { + "name": "OBS Studio", + "icon": "obs", + "description": "Open Source Streaming/Recording Application", + "pkg": "obs-studio", + "extra": [] + }, + { + "name": "Simple Screen Recorder", + "icon": "simple-ccsm", + "description": "Screen Capturing Application", + "pkg": "simplescreenrecorder", + "extra": [] + } + ] + } +] diff --git a/data/application_utility/default.json b/data/application_utility/default.json new file mode 100644 index 0000000..dbd78aa --- /dev/null +++ b/data/application_utility/default.json @@ -0,0 +1,874 @@ +[ + { + "name": "Browsers", + "icon": "browser", + "description": "Web browsing and communication", + "apps": [ + { + "name": "Vivaldi", + "icon": "vivaldi", + "description": "Configurable browser for power users", + "pkg": "vivaldi", + "extra": [] + }, + { + "name": "Chromium", + "icon": "chromium", + "description": "Fast and popular open source browser", + "pkg": "chromium", + "extra": [] + }, + { + "name": "Epiphany", + "icon": "epiphany", + "description": "Gnome Web Browser", + "pkg": "epiphany", + "filter": [ + "advanced" + ], + "extra": [] + }, + { + "name": "Firefox", + "icon": "mozilla-firefox", + "description": "Privacy oriented Web Browser", + "pkg": "firefox", + "extra": ["firefox-gnome-theme-maia"] + }, + { + "name": "Midori", + "icon": "midori", + "description": "Lightweight Web browser", + "pkg": "midori", + "extra": [] + }, + { + "name": "Falkon", + "icon": "falkon", + "description": "KDE Web Browser", + "pkg": "falkon", + "filter": [ + "advanced" + ], + "desktop": [ + "!gnome" + ], + "extra": [] + }, + { + "name": "Konqueror", + "icon": "konqueror", + "description": "Konqueror Web Browser", + "pkg": "konqueror", + "filter": [ + "advanced" + ], + "desktop": [ + "!gnome" + ], + "extra": [] + }, + { + "name": "Opera", + "icon": "opera", + "description": "Fast and secure webbrowser", + "pkg": "opera", + "extra": [] + } + ] + }, + { + "name": "E-mail", + "icon": "mail-client", + "description": "E-mail, Calendar, Tasks", + "apps": [ + { + "name": "Claws Mail", + "icon": "claws-mail", + "description": "Lightweight and fast GTK+ based Mail Client", + "pkg": "claws-mail", + "filter": [ + "advanced" + ], + "extra": [] + }, + { + "name": "Evolution", + "icon": "evolution", + "description": "Manage your email, contacts and schedule", + "pkg": "evolution", + "extra": [] + }, + { + "name": "Geary", + "icon": "geary", + "description": "Send and receive mail", + "pkg": "geary", + "extra": [] + }, + { + "name": "KMail", + "icon": "kmail", + "description": "KDE E-mail client", + "pkg": "kmail", + "desktop": [ + "!gnome" + ], + "extra": [] + }, + { + "name": "Sylpheed", + "icon": "sylpheed", + "description": "E-mail client", + "pkg": "sylpheed", + "extra": [] + }, + { + "name": "Thunderbird", + "icon": "thunderbird", + "description": "Send and receive mail, contacts and schedule", + "pkg": "thunderbird", + "extra": [] + } + ] + }, + { + "name": "Office Suites", + "icon": "applications-office", + "description": "Office suites like MS Office", + "apps": [ + { + "name": "FreeOffice", + "icon": "ms-word", + "description": "Microsoft Office compatible suite", + "pkg": "freeoffice", + "extra": [] + }, + { + "name": "Libre Office (Fresh)", + "icon": "libreoffice-main", + "description": "Open Source Office Application (Latest)", + "pkg": "libreoffice-fresh", + "extra": [] + }, + { + "name": "Libre Office (Still)", + "icon": "libreoffice-main", + "description": "Open Source Office Application (Stable)", + "pkg": "libreoffice-still", + "extra": [] + }, + { + "name": "MS Office Online", + "icon": "ms-word", + "description": "Microsoft Office Online", + "pkg": "microsoft-office-web-jak", + "extra": [] + }, + { + "name": "OnlyOffice Desktop Editors", + "icon": "ms-word", + "description": "Light and free MS Office clone.", + "pkg": "onlyoffice-desktopeditors", + "extra": [] + } + ] + }, + { + "name": "Extended language support", + "icon": "gnumeric", + "description": "Manjaro extended language support", + "apps": [ + { + "name": "Manjaro Asian Input Support Fcitx", + "icon": "gnumeric", + "description": "Manjaro extended language support with fcitx support", + "pkg": "manjaro-asian-input-support-fcitx", + "extra": [] + }, + { + "name": "Manjaro Asian Input Support Ibus", + "icon": "gnumeric", + "description": "Manjaro extended language support with ibus support", + "pkg": "manjaro-asian-input-support-ibus", + "extra": [] + } + ] + }, + { + "name": "E-Book", + "icon": "calibre", + "description": "E-book library apps", + "apps": [ + { + "name": "Calibre", + "icon": "calibre-viewer", + "description": "The one stop solution to your e-book needs", + "pkg": "calibre", + "extra": [] + }, + { + "name": "FBReader", + "icon": "fbreader", + "description": "FBReader E-Book Reader", + "pkg": "fbreader", + "extra": [] + } + ] + }, + { + "name": "Text Editors", + "icon": "text-editor", + "description": "Various editors for text or code", + "apps": [ + { + "name": "Atom", + "icon": "atom", + "description": "A hackable text editor for the 21st Century", + "pkg": "atom", + "filter": [ + "advanced" + ], + "extra": [] + }, + { + "name": "Gedit", + "icon": "gedit", + "description": "Gnome text editor.", + "pkg": "gedit", + "extra": [] + }, + { + "name": "Micro", + "icon": "micro", + "description": "Inituitive Text Editor for terminal", + "pkg": "micro-manjaro", + "extra": [] + }, + { + "name": "Vim", + "icon": "gvim", + "description": "Keyboard controlled text-editor", + "pkg": "gvim", + "filter": [ + "advanced" + ], + "extra": [] + }, + { + "name": "Visual Studio Code OSS", + "icon": "visual-studio-code", + "description": "Microsoft Code Editor", + "pkg": "code", + "filter": [ + "advanced" + ], + "extra": [] + }, + { + "name": "Gnome builder", + "icon": "gedit", + "description": "An IDE for writing GNOME-based software", + "pkg": "gnome-builder", + "filter": [ + "advanced" + ], + "extra": [] + }, + { + "name": "Xed", + "icon": "xed", + "description": "A small and lightweight text editor. X Apps Project", + "pkg": "xed", + "extra": [] + } + ] + }, + { + "name": "Printing", + "icon": "printer", + "description": "Support for printing CUPS, HPLIP", + "apps": [ + { + "name": "Manjaro Printing", + "icon": "printer", + "description": "Printing Meta Package", + "pkg": "manjaro-printer", + "extra": [] + } + ] + }, + { + "name": "PDF", + "icon": "pdfeditor", + "description": "PDF applications applications", + "apps": [ + { + "name": "Evince", + "icon": "evince", + "description": "View multi page documents", + "pkg": "evince", + "extra": [] + }, + { + "name": "Okular", + "icon": "okular", + "description": "Document Viewer", + "pkg": "okular", + "desktop": [ + "!gnome" + ], + "extra": [] + }, + + { + "name": "PDFMod", + "icon": "pdfmod", + "description": "Remove, extract and rotate pages in PDF Documents", + "pkg": "pdfmod", + "extra": [] + }, + { + "name": "Xournalpp", + "icon": "xournalpp", + "description": "Easy pdf editing tool", + "pkg": "xournalpp", + "extra": [] + }, + { + "name": "Xreader", + "icon": "xreader", + "description": "Xreader is based on Atril and acts as document and PDF reader", + "pkg": "xreader", + "extra": [] + }, + { + "name": "Paperwork", + "icon": "paperwork", + "description": "Document manager for GNOME to manage scanned documents and PDFs", + "pkg": "paperwork", + "extra": [] + } + ] + }, + { + "name": "Graphics Creating", + "icon": "applications-accessories", + "description": "Creating and editing graphics", + "apps": [ + { + "name": "Blender", + "icon": "blender", + "description": "3D modeling and animation", + "pkg": "blender", + "extra": [] + }, + { + "name": "GIMP", + "icon": "gimp", + "description": "Create images and edit photographs", + "pkg": "gimp", + "extra": [] + }, + { + "name": "Inkscape", + "icon": "inkscape", + "description": "Vector Graphics Editor", + "pkg": "inkscape", + "extra": [] + }, + { + "name": "Krita", + "icon": "krita", + "description": "Digital Painting Creative Freedom", + "pkg": "krita", + "extra": [] + }, + { + "name": "Pinta", + "icon": "pinta", + "description": "Easy create and edit images", + "pkg": "pinta", + "extra": [] + }, + { + "name": "Tux Paint", + "icon": "tuxpaint", + "description": "Drawing program for children", + "pkg": "tuxpaint", + "extra": [] + } + ] + }, + { + "name": "Photos", + "icon": "applications-graphics", + "description": "Viewers and organizers", + "apps": [ + { + "name": "Gpicview", + "icon": "gpicview", + "description": "Lightweight Image Viewer", + "pkg": "gpicview", + "extra": [] + }, + { + "name": "gThumb", + "icon": "gthumb", + "description": "View and organize your images", + "pkg": "gthumb", + "extra": [] + }, + { + "name": "Gnome-photos", + "icon": "gthumb", + "description": "Access, organize, and share your photos on GNOME", + "pkg": "gnome-photos", + "extra": [] + }, + { + "name": "Ristretto", + "icon": "ristretto", + "description": "Free and lightweight image viewer", + "pkg": "ristretto", + "extra": [] + }, + { + "name": "Shotwell", + "icon": "shotwell", + "description": "Popular Photo Manager", + "pkg": "shotwell", + "extra": [] + }, + { + "name": "Viewnior", + "icon": "viewnior", + "description": "GTK based Elegant Image Viewer", + "pkg": "viewnior", + "extra": [] + }, + { + "name": "Eye of Gnome", + "icon": "eog", + "description": "An image viewing and cataloging program", + "pkg": "eog", + "extra": [] + } + ] + }, + { + "name": "Video/Movie", + "icon": "video-player", + "description": "Organize and play videos and movies", + "apps": [ + { + "name": "Kodi", + "icon": "kodi", + "description": "Manage and view your media", + "pkg": "kodi", + "extra": [] + }, + { + "name": "Parole", + "icon": "parole", + "description": "Modern and simple media player", + "pkg": "parole", + "extra": [] + }, + { + "name": "SM Player", + "icon": "smplayer", + "description": "A great MPlayer front end", + "pkg": "smplayer", + "extra": [] + }, + { + "name": "Totem", + "icon": "totem", + "description": "Play movies", + "pkg": "totem", + "extra": [] + }, + { + "name": "VLC", + "icon": "vlc", + "description": "VLC media player, the openxource multimedia player", + "pkg": "vlc", + "extra": [] + }, + { + "name": "Xplayer", + "icon": "xplayer", + "description": "Xplayer is based on Totem and plays both music an video", + "pkg": "xplayer", + "extra": [] + } + ] + }, + { + "name": "Audio", + "icon": "musicbrainz", + "description": "Audio players", + "apps": [ + { + "name": "Audacious", + "icon": "audacious", + "description": "Listen to music", + "pkg": "audacious", + "extra": [] + }, + { + "name": "Clementine", + "icon": "clementine", + "description": "Play music files and internet radio", + "pkg": "clementine", + "extra": [] + }, + { + "name": "DeadBeeF", + "icon": "deadbeef", + "description": "Listen to music", + "pkg": "deadbeef", + "extra": ["ffmpeg", "libmad", "mpg123"] + }, + { + "name": "Lollypop", + "icon": "lollypop", + "description": "Play and organize your music collection", + "pkg": "lollypop", + "extra": [] + }, + { + "name": "Rhythmbox", + "icon": "rhythmbox", + "description": "Gnome music playing application", + "pkg": "rhythmbox", + "extra": [] + } + ] + }, + { + "name": "Media recording/editing", + "icon": "kdenlive", + "description": "Audio and Video editing", + "apps": [ + { + "name": "Audacity", + "icon": "audacity", + "description": "Record and Edit Audio files", + "pkg": "audacity", + "extra": [] + }, + { + "name": "Kdenlive", + "icon": "kdenlive", + "description": "Video Editor", + "pkg": "kdenlive", + "extra": [] + }, + { + "name": "OBS Studio", + "icon": "obs", + "description": "Open Source Streaming/Recording Application", + "pkg": "obs-studio", + "extra": [] + }, + { + "name": "Shotcut", + "icon": "shotcut", + "description": "Cross-platform Qt based Video Editor", + "pkg": "shotcut", + "extra": [] + }, + { + "name": "Openshot", + "icon": "openshot", + "description": "Award-winning free and open-source video editor", + "pkg": "obs-studio", + "extra": [] + }, + { + "name": "Pitivi", + "icon": "pitivi", + "description": "Editor for audio/video projects", + "pkg": "pitivi", + "extra": [] + }, + { + "name": "Simple Screen Recorder", + "icon": "simple-ccsm", + "description": "Screen Capturing Application", + "pkg": "simplescreenrecorder", + "extra": [] + }, + { + "name": "Flowblade", + "icon": "flowblade", + "description": "Multitrack non-linear video editor", + "pkg": "flowblade", + "extra": [] + } + ] + }, + { + "name": "Chat", + "icon": "internet-chat", + "description": "Online messaging and chat", + "apps": [ + { + "name": "HexChat", + "icon": "hexchat", + "description": "Graphic IRC Client", + "pkg": "hexchat", + "extra": [] + }, + { + "name": "Telegram", + "icon": "telegram-desktop", + "description": "Official Telegram Desktop client", + "pkg": "telegram-desktop", + "extra": [] + }, + { + "name": "Whatsapp web", + "icon": "whatsapp-web-jak", + "description": "Wrapper for whatsapp online", + "pkg": "whatsapp-web-jak", + "extra": [] + }, + { + "name": "Slack web", + "icon": "slack-web-jak", + "description": "Wrapper for slack online", + "pkg": "slack-web-jak", + "extra": [] + }, + { + "name": "Pidgin Messenger", + "icon": "pidgin", + "description": "Instant messaging Client", + "pkg": "pidgin", + "extra": [] + } + ] + }, + { + "name": "File Sharing", + "icon": "transmission", + "description": "FTP and Torrent apps", + "filter": [ + "advanced" + ], + "apps": [ + { + "name": "Deluge", + "icon": "deluge", + "description": "Python based Bittorrent Client", + "pkg": "deluge", + "extra": [] + }, + { + "name": "Filezilla", + "icon": "filezilla", + "description": "Graphical FTP/FTPS/SFTP browser", + "pkg": "filezilla", + "extra": [] + }, + { + "name": "Transmission GTK", + "icon": "transmission", + "description": "GTK based Bittorrent Client", + "pkg": "transmission-gtk", + "extra": [] + }, + { + "name": "QTransmission", + "icon": "transmission", + "description": "QT based Bittorrent Client", + "pkg": "transmission-qt", + "desktop": [ + "!gnome" + ], + "extra": [] + }, + { + "name": "Syncthing", + "icon": "syncthing-gtk", + "description": "Cross Platform file synchronization tool", + "pkg": "syncthing-gtk", + "extra": [] + } + ] + }, + { + "name": "Backup", + "icon": "deja-dup", + "description": "Backup utilites", + "apps": [ + { + "name": "Deja Dup", + "icon": "deja-dup", + "description": "Keep your important documents safe from disater", + "pkg": "deja-dup", + "extra": [] + }, + { + "name": "Grsync", + "icon": "grsync", + "description": "Synchronize files and folders", + "pkg": "grsync", + "filter": [ + "advanced" + ], + "extra": [] + }, + { + "name": "Timeshift", + "icon": "timeshift", + "description": "A system restore utility for Linux", + "pkg": "timeshift", + "extra": [] + }, + { + "name": "Backintime", + "icon": "deja-dup", + "description": "Simple backup system", + "pkg": "backintime", + "extra": [] + }, + { + "name": "Borg", + "icon": "deja-dup", + "description": "Deduplicating backup program with compression and authenticated encryption", + "pkg": "borg", + "filter": [ + "advanced" + ], + "extra": [] + } + ] + }, + { + "name": "Password Manager", + "icon": "security-high", + "description": "Password Managers", + "apps": [ + { + "name": "KeePassXC", + "icon": "keepassxc", + "description": "Cross Platform Password Manager", + "pkg": "keepassxc", + "extra": [] + }, + { + "name": "Bitwarden", + "icon": "bitwarden", + "description": "Secure and free cloud based password manager.", + "pkg": "bitwarden", + "extra": [] + }, + { + "name": "Enpass", + "icon": "enpass", + "description": "Premium Cross-platform Password Manager", + "pkg": "enpass", + "extra": [] + } + ] + }, + { + "name": "System Tools", + "icon": "disk-utility", + "description": "System utilities", + "filter": [ + "advanced" + ], + "apps": [ + { + "name": "Popsicle", + "icon": "mintstick", + "description": "Graphical tool to write ISO to USB", + "pkg": "popsicle", + "extra": [] + }, + { + "name": "Gnome Disks", + "icon": "gnome-disks", + "description": "Disk management system for Gnome", + "pkg": "gnome-disk-utility", + "desktop": [ + "!kde" + ], + "extra": [] + }, + { + "name": "Gparted", + "icon": "gparted", + "description": "Create, reorganize, and delete partitions", + "pkg": "gparted", + "extra": [] + }, + { + "name": "IsoUSB", + "icon": "mintstick", + "description": "A graphical tool to copy a hybrid ISO onto a USB key.", + "pkg": "isousb", + "extra": [] + }, + { + "name": "Mintstick", + "icon": "mintstick", + "description": "Format or write imges to usb sticks (Linux Mint tool).", + "pkg": "mintstick", + "extra": [] + }, + { + "name": "Ventoy", + "icon": "mintstick", + "description": "Tool for creating multiboot isos", + "pkg": "ventoy", + "extra": [] + }, + { + "name": "Alma", + "icon": "mintstick", + "description": "Tool for creating persistent live isos", + "pkg": "alma-git", + "extra": [] + } + ] + }, + { + "name": "Virtual Computing", + "icon": "virt-manager", + "description": "Virtual Machine applications", + "filter": [ + "advanced" + ], + "apps": [ + { + "name": "VirtualBox", + "icon": "virtualbox", + "description": "Run several virtual systems on a single host computer", + "pkg": "virtualbox", + "extra": [ + "virtualbox-guest-iso" + ] + }, + { + "name": "Gnome Boxes", + "icon": "gnome-boxes", + "description": "Simple remote and virtual machines", + "pkg": "gnome-boxes", + "extra": [] + }, + { + "name": "Virt-manager", + "icon": "virt-manager", + "description": "Desktop user interface for managing virtual machines", + "pkg": "virt-manager", + "extra": [] + } + ] + } +] diff --git a/meson.build b/meson.build index 1323ae9..f740aa3 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('cachyos-hello', 'rust', - version: '0.7.0', + version: '0.8.0', license: 'GPLv3', meson_version: '>=0.56.0', default_options: ['buildtype=debugoptimized', diff --git a/src/alpm_helper.rs b/src/alpm_helper.rs new file mode 100644 index 0000000..faa2230 --- /dev/null +++ b/src/alpm_helper.rs @@ -0,0 +1,103 @@ +#[derive(Clone, Debug)] +#[repr(C)] +pub struct AlpmHelper { + pub pkg_list_install: Vec, + pub pkg_list_removal: Vec, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum AlpmHelperResult { + NOTHING, + REMOVE, + ADD, + BOTH, +} + +impl AlpmHelper { + pub fn new() -> Self { + Self { pkg_list_install: Vec::new(), pkg_list_removal: Vec::new() } + } + + pub fn clear(&mut self) { + self.pkg_list_install.clear(); + self.pkg_list_removal.clear(); + } + + pub fn is_empty(&self) -> bool { + self.pkg_list_install.is_empty() && self.pkg_list_removal.is_empty() + } + + pub fn do_update(&self) -> AlpmHelperResult { + let mut result = AlpmHelperResult::NOTHING; + if self.pkg_list_install.is_empty() && self.pkg_list_removal.is_empty() { + return result; + } + + if !self.pkg_list_removal.is_empty() { + if self.install_apps(&self.pkg_list_removal, false) { + result = AlpmHelperResult::REMOVE; + } + } + if !self.pkg_list_install.is_empty() { + if self.install_apps(&self.pkg_list_install, true) { + if result == AlpmHelperResult::NOTHING { + result = AlpmHelperResult::ADD; + } else { + result = AlpmHelperResult::BOTH; + } + } + } + + result + } + + pub fn set_package(&mut self, pkg_name: &String, install: bool, installed: bool) { + if self.to_remove(&pkg_name) { + let index = self.pkg_list_removal.iter().position(|x| *x == *pkg_name).unwrap(); + self.pkg_list_removal.remove(index); + } else if !install && installed { + self.pkg_list_removal.push(String::from(pkg_name)); + } + + if self.to_install(&pkg_name) { + let index = self.pkg_list_install.iter().position(|x| *x == *pkg_name).unwrap(); + self.pkg_list_install.remove(index); + } else if install && !installed { + self.pkg_list_install.push(String::from(pkg_name)); + } + } + + pub fn to_install(&self, pkg_name: &String) -> bool { + self.pkg_list_install.contains(&pkg_name) + } + + pub fn to_remove(&self, pkg_name: &String) -> bool { + self.pkg_list_removal.contains(&pkg_name) + } + + fn install_apps(&self, pkg_list: &Vec, install: bool) -> bool { + let mut install_arg: &str = ""; + if pkg_list.is_empty() { + return false; + } else if !install { + install_arg = "-R"; + } + + println!("pacman {} {:?}", install_arg, pkg_list); + + match install { + true => !self.app_installed(&pkg_list[0]), + false => self.app_installed(&pkg_list[0]), + } + } + + fn app_installed(&self, pkg_name: &str) -> bool { + let pacman = + pacmanconf::Config::with_opts(None, Some("/etc/pacman.conf"), Some("/")).unwrap(); + let alpm = alpm_utils::alpm_with_conf(&pacman).unwrap(); + match alpm.localdb().pkg(pkg_name) { + Ok(_) => true, + _ => false, + } + } +} diff --git a/src/application_browser.rs b/src/application_browser.rs new file mode 100644 index 0000000..d0b88c9 --- /dev/null +++ b/src/application_browser.rs @@ -0,0 +1,426 @@ +use crate::alpm_helper::*; +use crate::config::PKGDATADIR; +use crate::utils; + +use gio::prelude::*; +use gtk::prelude::{ + BoxExt, ButtonExt, CellRendererExt, CellRendererToggleExt, ComboBoxExt, ContainerExt, GridExt, + GtkListStoreExt, GtkListStoreExtManual, ScrolledWindowExt, ToggleButtonExt, TreeModelExt, + TreeStoreExt, TreeStoreExtManual, TreeViewColumnExt, TreeViewExt, WidgetExt, +}; + +use once_cell::sync::Lazy; +use serde_json; +use std::fs; +use std::sync::Mutex; + +#[derive(Debug)] +pub struct ApplicationBrowser { + pub alpm_handle: alpm::Alpm, + pub alpm_helper: AlpmHelper, + pub filter: bool, + pub app_store: gtk::TreeStore, + pub group_store: gtk::ListStore, + pub group_tofilter: String, + pub groups: serde_json::Value, + pub tree_view: gtk::TreeView, + pub app_browser_box: gtk::Box, + pub button_box: gtk::Box, + pub update_system_btn: gtk::Button, +} + +fn new_alpm() -> alpm::Result { + let pacman = pacmanconf::Config::with_opts(None, Some("/etc/pacman.conf"), Some("/")).unwrap(); + let alpm = alpm_utils::alpm_with_conf(&pacman)?; + + Ok(alpm) +} + +const GROUP: u32 = 0; +const ICON: u32 = 1; +const APPLICATION: u32 = 2; +const DESCRIPTION: u32 = 3; +const ACTIVE: u32 = 4; +const PACKAGE: u32 = 5; +const INSTALLED: u32 = 6; + +// static mut G_APP_BROWSER: Rc> = +// Rc::new(Mutex::new(ApplicationBrowser::new())); +static mut G_APP_BROWSER: Lazy> = Lazy::new(|| { + let mut app_browser = ApplicationBrowser::new(); + app_browser.create_page(); + Mutex::new(app_browser) +}); + +impl ApplicationBrowser { + pub fn new() -> Self { + let app_browser_box = gtk::Box::new(gtk::Orientation::Vertical, 0); + app_browser_box.set_expand(true); + + let button_box = gtk::Box::new(gtk::Orientation::Horizontal, 10); + let advanced_button = gtk::ToggleButton::with_label("advanced"); + advanced_button.set_tooltip_text(Some("Toggle an extended selection of packages")); + advanced_button.connect_clicked(on_advanced_clicked); + // let download_button = gtk::Button::with_label("download"); + // download_button.set_tooltip_text(Some("Download the most recent selection of packages")); + // download_button.connect_clicked(on_download_clicked); + let reset_button = gtk::Button::with_label("reset"); + reset_button.set_tooltip_text(Some("Reset your current selections...")); + reset_button.connect_clicked(on_reload_clicked); + let update_system_btn = gtk::Button::with_label("UPDATE SYSTEM"); + update_system_btn.set_tooltip_text(Some("Apply your current selections to the system")); + update_system_btn.connect_clicked(on_update_system_clicked); + update_system_btn.set_sensitive(false); + + // Group filter + let data = + fs::read_to_string(format!("{}/data/application_utility/default.json", PKGDATADIR)) + .expect("Unable to read file"); + let groups: serde_json::Value = serde_json::from_str(&data).expect("Unable to parse"); + let group_store = load_groups_data(&groups); + let group_combo = utils::create_combo_with_model(&group_store); + group_combo.connect_changed(on_group_filter_changed); + + // Packing button box + button_box.pack_start(&advanced_button, false, false, 10); + button_box.pack_start(&group_combo, false, false, 10); + button_box.pack_end(&update_system_btn, false, false, 10); + + button_box.pack_end(&reset_button, false, false, 10); + // button_box.pack_end(&download_button, false, false, 10); + app_browser_box.pack_start(&button_box, false, false, 10); + + let col_types: [glib::Type; 7] = [ + String::static_type(), + String::static_type(), + String::static_type(), + String::static_type(), + bool::static_type(), + String::static_type(), + bool::static_type(), + ]; + + Self { + alpm_handle: new_alpm().unwrap(), + alpm_helper: AlpmHelper::new(), + filter: false, + app_store: gtk::TreeStore::new(&col_types), + group_store, + groups, + group_tofilter: String::from("*"), + tree_view: gtk::TreeView::new(), + app_browser_box, + button_box, + update_system_btn, + } + } + + pub fn default_impl() -> &'static Mutex { + unsafe { &G_APP_BROWSER } + } + + fn load_app_data(&mut self) -> usize { + // not use data set for the moment + let mut store_size: usize = 0; + + let localdb = self.alpm_handle.localdb(); + + for group in self.groups.as_array().unwrap() { + if let Some(apps_map) = group.get("apps") { + let g_name = String::from(group["name"].as_str().unwrap()); + let g_icon = String::from(group["icon"].as_str().unwrap()); + let mut g_desc = String::from(group["description"].as_str().unwrap()); + if g_desc.len() < 72 { + g_desc += " "; + } + + if self.group_tofilter != "*" && self.group_tofilter != g_name { + continue; + } + if group["filter"].as_array() != None && !self.filter { + continue; + } + + let index = self.app_store.insert_with_values(None, None, &[ + (GROUP, &None::), + (ICON, &g_icon), + (APPLICATION, &g_name), + (DESCRIPTION, &g_desc), + (ACTIVE, &false), + (PACKAGE, &None::), + (INSTALLED, &false), + ]); + store_size += 1; + + for app in apps_map.as_array().unwrap() { + let app_name = String::from(app["pkg"].as_str().unwrap()); + let mut status = localdb.pkg(app_name).is_ok(); + + if app["filter"].as_array() != None && !self.filter { + continue; + } + + // Restore user checks + if !status + && self.alpm_helper.to_install(&String::from(app["pkg"].as_str().unwrap())) + { + status = true; + } + if status + && self.alpm_helper.to_remove(&String::from(app["pkg"].as_str().unwrap())) + { + status = false; + } + + self.app_store.insert_with_values(Some(&index), None, &[ + (GROUP, &None::), + (ICON, &String::from(app["icon"].as_str().unwrap())), + (APPLICATION, &String::from(app["name"].as_str().unwrap())), + (DESCRIPTION, &String::from(app["description"].as_str().unwrap())), + (ACTIVE, &status), + (PACKAGE, &String::from(app["pkg"].as_str().unwrap())), + (INSTALLED, &status), + ]); + } + } + } + + store_size + } + + pub fn reload_app_data(&mut self, refresh: bool) { + self.alpm_helper.clear(); + self.app_store.clear(); + + if refresh { + self.group_store = load_groups_data(&self.groups); + } + self.load_app_data(); + self.tree_view.set_model(Some(&self.app_store)); + self.update_system_btn.set_sensitive(!self.alpm_helper.is_empty()); + } + + fn create_view_tree(&mut self) -> usize { + // setup list store model + let app_store_size = self.load_app_data(); + + // create a tree view with the model store + self.tree_view = gtk::TreeView::with_model(&self.app_store); + self.tree_view.set_activate_on_single_click(true); + self.tree_view.set_has_tooltip(true); + self.tree_view.connect_query_tooltip(on_query_tooltip_tree_view); + + // column model: icon + let icon = gtk::CellRendererPixbuf::new(); + let icon_column = create_column("", &icon, "icon_name", ICON); + self.tree_view.append_column(&icon_column); + + // column model: group name column + // let group_cell_renderer = gtk::CellRendererText::new(); + // let group_column = create_column("Group", &group_cell_renderer, "text", APPLICATION); + // tree_view.append_column(&group_column); + // group_column + // .set_cell_data_func(&group_cell_renderer, + // Some(Box::new(treeview_cell_app_data_function))); + + // column model: app name column + let app_cell_renderer = gtk::CellRendererText::new(); + let app_column = create_column("Application", &app_cell_renderer, "text", APPLICATION); + // app_column.set_resizable(false); + app_column.set_cell_data_func( + &app_cell_renderer, + Some(Box::new(treeview_cell_app_data_function)), + ); + self.tree_view.append_column(&app_column); + + // column model: description column + let desc_renderer = gtk::CellRendererText::new(); + let desc_column = create_column("Description", &desc_renderer, "text", DESCRIPTION); + desc_column.set_resizable(true); + self.tree_view.append_column(&desc_column); + + // column model: install column + let install_renderer = gtk::CellRendererToggle::new(); + install_renderer.connect_toggled(on_app_toggle); + let install_column = create_column("Install/Remove", &install_renderer, "active", ACTIVE); + install_column.set_cell_data_func( + &install_renderer, + Some(Box::new(treeview_cell_check_data_function)), + ); + + install_column.set_resizable(false); + install_column.set_max_width(40); + install_column.set_fixed_width(40); + self.tree_view.append_column(&install_column); + + app_store_size + } + + pub fn get_page(&self) -> >k::Box { + &self.app_browser_box + } + + fn create_page(&mut self) { + // create view and app store + let app_store_size = self.create_view_tree(); + // create a scrollable window + let app_window = gtk::ScrolledWindow::new(gtk::Adjustment::NONE, gtk::Adjustment::NONE); + app_window.set_vexpand(true); + app_window.set_policy(gtk::PolicyType::Never, gtk::PolicyType::Automatic); + // add window to tree view + app_window.add(&self.tree_view); + + // setup grid + let grid_inter = gtk::Grid::new(); + grid_inter.set_column_homogeneous(true); + grid_inter.set_row_homogeneous(true); + // add grid to app browser + self.app_browser_box.add(&grid_inter); + grid_inter.attach(&app_window, 0, 0, 5, app_store_size as i32); + } + + pub fn get_alpm_handle(&self) -> &alpm::Alpm { + &self.alpm_handle + } +} + +fn treeview_cell_app_data_function( + _column: >k::TreeViewColumn, + renderer_cell: >k::CellRenderer, + model: >k::TreeModel, + iter_a: >k::TreeIter, +) { + let value_gobj = model.value(iter_a, INSTALLED as i32); + let value = value_gobj.get::().unwrap(); + if value { + renderer_cell.set_width(600); + } else { + renderer_cell.set_width(400); + } +} + +fn treeview_cell_check_data_function( + _column: >k::TreeViewColumn, + renderer_cell: >k::CellRenderer, + model: >k::TreeModel, + iter_a: >k::TreeIter, +) { + let value_gobj = model.value(iter_a, GROUP as i32); + let value = value_gobj.get::<&str>().is_ok(); + renderer_cell.set_visible(!value); +} + +fn on_reload_clicked(_button: >k::Button) { + let app_browser = unsafe { &mut G_APP_BROWSER.lock().unwrap() }; + app_browser.reload_app_data(false); +} + +fn on_group_filter_changed(combo: >k::ComboBox) { + let app_browser = unsafe { &mut G_APP_BROWSER.lock().unwrap() }; + if let Some(tree_iter) = combo.active_iter() { + let model = combo.model().unwrap(); + let group_gobj = model.value(&tree_iter, 0); + let group = group_gobj.get::<&str>().unwrap(); + app_browser.group_tofilter = String::from(group); + app_browser.app_store.clear(); + app_browser.load_app_data(); + app_browser.tree_view.set_model(Some(&app_browser.app_store)); + if group != "*" { + app_browser.tree_view.expand_all(); + } + } +} + +fn on_advanced_clicked(button: >k::ToggleButton) { + let app_browser = unsafe { &mut G_APP_BROWSER.lock().unwrap() }; + let is_active = button.is_active(); + app_browser.filter = is_active; + app_browser.reload_app_data(false); +} + +fn on_query_tooltip_tree_view( + treeview: >k::TreeView, + x_f: i32, + y_f: i32, + keyboard_tip: bool, + tooltip: >k::Tooltip, +) -> bool { + let mut x = x_f; + let mut y = y_f; + let tooltip_context = treeview.tooltip_context(&mut x, &mut y, keyboard_tip); + if let Some((model_tmp, path, iter_a)) = tooltip_context { + let model = model_tmp.unwrap(); + let value_gobj = model.value(&iter_a, INSTALLED as i32); + let value = value_gobj.get::(); + if value.is_ok() && value.unwrap() { + let mut msg = String::from("Installed"); + let active_gobj = model.value(&iter_a, ACTIVE as i32); + let active = active_gobj.get::(); + if active.is_ok() && !active.unwrap() { + msg.push_str(" , to remove"); + } + tooltip.set_markup(Some(msg.as_str())); + treeview.set_tooltip_row(tooltip, &path); + return true; + } + } + false +} + +fn on_app_toggle(_cell: >k::CellRendererToggle, path: gtk::TreePath) { + let app_browser = unsafe { &mut G_APP_BROWSER.lock().unwrap() }; + let app_store = app_browser.app_store.clone(); + let iter_a = app_store.iter(&path).unwrap(); + let value_gobj = app_store.value(&iter_a, PACKAGE as i32); + + // a group has no package attached and we don't install groups + if value_gobj.get::<&str>().is_ok() { + let toggle_a = app_store.value(&iter_a, ACTIVE as i32).get::().unwrap(); + app_store.set(&iter_a, &[(ACTIVE, &!toggle_a)]); + + let alpm_handle = app_browser.get_alpm_handle(); + let update_system_button = app_browser.update_system_btn.clone(); + let localdb = alpm_handle.localdb(); + let pkg = app_store.value(&iter_a, PACKAGE as i32).get::().unwrap(); + + let installed = localdb.pkg(pkg.clone()).is_ok(); + // update lists + app_browser.alpm_helper.set_package(&pkg, !toggle_a, installed); + update_system_button.set_sensitive(!app_browser.alpm_helper.is_empty()); + } +} + +fn on_update_system_clicked(_: >k::Button) { + let app_browser = unsafe { &mut G_APP_BROWSER.lock().unwrap() }; + if app_browser.alpm_helper.do_update() != AlpmHelperResult::NOTHING { + // reload json for view new apps installed + app_browser.reload_app_data(true); + } +} + +fn load_groups_data(groups: &serde_json::Value) -> gtk::ListStore { + // not use data set for the moment + let store = gtk::ListStore::new(&[String::static_type()]); + store.set(&store.append(), &[(0, &String::from("*"))]); + + for group in groups.as_array().unwrap() { + let g_name = String::from(group["name"].as_str().unwrap()); + store.set(&store.append(), &[(0, &String::from(g_name))]); + } + + store +} + +fn create_column( + title: &str, + cell: &impl IsA, + attr: &str, + val: u32, +) -> gtk::TreeViewColumn { + let column = gtk::TreeViewColumn::new(); + column.set_title(title); + column.pack_start(cell, true); + column.add_attribute(cell, attr, val as i32); + + column +} diff --git a/src/main.rs b/src/main.rs index 98a710a..7e3a263 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,11 @@ #![feature(const_str_from_utf8)] #![allow(non_upper_case_globals)] +mod alpm_helper; +mod application_browser; mod config; mod data_types; +mod pages; mod utils; use config::{APP_ID, GETTEXT_PACKAGE, LOCALEDIR, PKGDATADIR, PROFILE, VERSION}; @@ -12,9 +15,8 @@ use gettextrs::LocaleCategory; use gtk::{gio, glib, Builder, HeaderBar, Window}; use once_cell::sync::Lazy; use std::collections::HashMap; -use std::fmt::Write as _; use std::path::Path; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; use utils::*; use gio::prelude::*; @@ -23,14 +25,10 @@ use gtk::prelude::*; use gdk_pixbuf::Pixbuf; use serde_json::json; -use std::sync::Arc; use std::{fs, str}; -use subprocess::{Exec, Redirection}; +use subprocess::Exec; static mut g_save_json: Lazy> = Lazy::new(|| Mutex::new(json!(null))); -static mut g_local_units: Lazy> = Lazy::new(|| Mutex::new(SystemdUnits::new())); -static mut g_global_units: Lazy> = - Lazy::new(|| Mutex::new(SystemdUnits::new())); static mut g_hello_window: Option> = None; @@ -71,84 +69,6 @@ fn quick_message(message: &str) { } } -fn create_options_section() -> gtk::Box { - let topbox = gtk::Box::new(gtk::Orientation::Vertical, 2); - let box_collection = gtk::Box::new(gtk::Orientation::Horizontal, 10); - let label = gtk::Label::new(None); - label.set_line_wrap(true); - label.set_justify(gtk::Justification::Center); - label.set_text("Tweaks"); - - let psd_btn = gtk::CheckButton::with_label("Profile-sync-daemon enable"); - let systemd_oomd_btn = gtk::CheckButton::with_label("Systemd-oomd enabled"); - let apparmor_btn = gtk::CheckButton::with_label("Apparmor enabled"); - let ananicy_cpp_btn = gtk::CheckButton::with_label("Ananicy Cpp enabled"); - - unsafe { - psd_btn.set_data("actionData", "psd.service"); - psd_btn.set_data("actionType", "user_service"); - systemd_oomd_btn.set_data("actionData", "systemd-oomd.service"); - systemd_oomd_btn.set_data("actionType", "service"); - apparmor_btn.set_data("actionData", "apparmor.service"); - apparmor_btn.set_data("actionType", "service"); - ananicy_cpp_btn.set_data("actionData", "ananicy-cpp.service"); - ananicy_cpp_btn.set_data("actionType", "service"); - } - - for btn in &[&psd_btn, &systemd_oomd_btn, &apparmor_btn, &ananicy_cpp_btn] { - unsafe { - let data: &str = *btn.data("actionData").unwrap().as_ptr(); - if g_local_units.lock().unwrap().enabled_units.contains(&String::from(data)) - || g_global_units.lock().unwrap().enabled_units.contains(&String::from(data)) - { - btn.set_active(true); - } - } - } - - psd_btn.connect_clicked(on_servbtn_clicked); - systemd_oomd_btn.connect_clicked(on_servbtn_clicked); - apparmor_btn.connect_clicked(on_servbtn_clicked); - ananicy_cpp_btn.connect_clicked(on_servbtn_clicked); - - topbox.pack_start(&label, true, false, 1); - box_collection.pack_start(&psd_btn, true, false, 2); - box_collection.pack_start(&systemd_oomd_btn, true, false, 2); - box_collection.pack_start(&apparmor_btn, true, false, 2); - box_collection.pack_start(&ananicy_cpp_btn, true, false, 2); - box_collection.set_halign(gtk::Align::Fill); - topbox.pack_start(&box_collection, true, false, 1); - - topbox.set_hexpand(true); - topbox -} - -fn create_apps_section() -> gtk::Box { - let topbox = gtk::Box::new(gtk::Orientation::Vertical, 2); - let box_collection = gtk::Box::new(gtk::Orientation::Horizontal, 10); - let label = gtk::Label::new(None); - label.set_line_wrap(true); - label.set_justify(gtk::Justification::Center); - label.set_text("Applications"); - - let cachyos_pi = gtk::Button::with_label("CachyOS PackageInstaller"); - let cachyos_km = gtk::Button::with_label("CachyOS Kernel Manager"); - - cachyos_pi.connect_clicked(on_appbtn_clicked); - cachyos_km.connect_clicked(on_appbtn_clicked); - - box_collection.pack_start(&cachyos_pi, true, true, 2); - box_collection.pack_start(&cachyos_km, true, true, 2); - - topbox.pack_start(&label, true, true, 2); - - box_collection.set_halign(gtk::Align::Fill); - topbox.pack_start(&box_collection, true, true, 0); - - topbox.set_hexpand(true); - topbox -} - fn show_about_dialog() { let main_window: Window; unsafe { @@ -364,50 +284,8 @@ fn build_ui(application: >k::Application) { let install: gtk::Button = builder.object("install").unwrap(); install.set_visible(false); } - let install: gtk::Button = builder.object("appBrowser").unwrap(); - install.set_visible(true); - - load_enabled_units(); - load_global_enabled_units(); - - let viewport = gtk::Viewport::new(gtk::Adjustment::NONE, gtk::Adjustment::NONE); - // let label = gtk::Label::new(None); - // label.set_line_wrap(true); - let image = gtk::Image::from_icon_name(Some("go-previous"), gtk::IconSize::Button); - let back_btn = gtk::Button::new(); - back_btn.set_image(Some(&image)); - back_btn.set_widget_name("home"); - - back_btn.connect_clicked(glib::clone!(@weak builder => move |button| { - let name = button.widget_name(); - let stack: gtk::Stack = builder.object("stack").unwrap(); - stack.set_visible_child_name(&format!("{}page", name)); - })); - - let options_section_box = create_options_section(); - let apps_section_box = create_apps_section(); - - let grid = gtk::Grid::new(); - grid.set_hexpand(true); - grid.set_margin_start(10); - grid.set_margin_end(10); - grid.set_margin_top(5); - grid.set_margin_bottom(5); - grid.attach(&back_btn, 0, 1, 1, 1); - let box_collection = gtk::Box::new(gtk::Orientation::Vertical, 5); - - box_collection.pack_start(&options_section_box, true, true, 5); - box_collection.pack_start(&apps_section_box, true, true, 5); - - box_collection.set_valign(gtk::Align::Center); - box_collection.set_halign(gtk::Align::Center); - grid.attach(&box_collection, 1, 2, 5, 1); - viewport.add(&grid); - viewport.show_all(); - - let stack: gtk::Stack = builder.object("stack").unwrap(); - let child_name = "appBrowserpage"; - stack.add_named(&viewport, child_name); + pages::create_appbrowser_page(&builder); + pages::create_tweaks_page(&builder); // Show the UI main_window.show(); @@ -575,54 +453,6 @@ fn get_page(name: &str, preferences: &serde_json::Value, save: &serde_json::Valu fs::read_to_string(filename).unwrap() } -fn load_enabled_units() { - unsafe { - g_local_units.lock().unwrap().loaded_units.clear(); - g_local_units.lock().unwrap().enabled_units.clear(); - - let mut exec_out = Exec::shell("systemctl list-unit-files -q --no-pager | tr -s \" \"") - .stdout(Redirection::Pipe) - .capture() - .unwrap() - .stdout_str(); - exec_out.pop(); - - let service_list = exec_out.split('\n'); - - for service in service_list { - let out: Vec<&str> = service.split(' ').collect(); - g_local_units.lock().unwrap().loaded_units.push(out[0].to_string()); - if out[1] == "enabled" { - g_local_units.lock().unwrap().enabled_units.push(out[0].to_string()); - } - } - } -} - -fn load_global_enabled_units() { - unsafe { - g_global_units.lock().unwrap().loaded_units.clear(); - g_global_units.lock().unwrap().enabled_units.clear(); - - let mut exec_out = - Exec::shell("systemctl --global list-unit-files -q --no-pager | tr -s \" \"") - .stdout(Redirection::Pipe) - .capture() - .unwrap() - .stdout_str(); - exec_out.pop(); - - let service_list = exec_out.split('\n'); - for service in service_list { - let out: Vec<&str> = service.split(' ').collect(); - g_global_units.lock().unwrap().loaded_units.push(out[0].to_string()); - if out[1] == "enabled" { - g_global_units.lock().unwrap().enabled_units.push(out[0].to_string()); - } - } - } -} - /// Handlers fn on_languages_changed(param: &[glib::Value]) -> Option { let widget = param[0].get::().unwrap(); @@ -652,103 +482,6 @@ fn on_action_clicked(param: &[glib::Value]) -> Option { }; } -fn on_servbtn_clicked(button: >k::CheckButton) { - // Get action data/type. - let action_type: &str; - let action_data: &str; - unsafe { - action_type = *button.data("actionType").unwrap().as_ptr(); - action_data = *button.data("actionData").unwrap().as_ptr(); - } - - let (user_only, pkexec_only) = - if action_type == "user_service" { ("--user", "--user $(logname)") } else { ("", "") }; - - let cmd: String; - unsafe { - let local_units = &g_local_units.lock().unwrap().enabled_units; - cmd = if !local_units.contains(&String::from(action_data)) { - format!( - "/sbin/pkexec {} bash -c \"systemctl {} enable --now --force {}\"", - pkexec_only, user_only, action_data - ) - } else { - format!( - "/sbin/pkexec {} bash -c \"systemctl {} disable --now {}\"", - pkexec_only, user_only, action_data - ) - }; - } - - // Spawn child process in separate thread. - std::thread::spawn(move || { - Exec::shell(cmd).join().unwrap(); - - if action_type == "user_service" { - load_global_enabled_units(); - } else { - load_enabled_units(); - } - }); -} - -fn on_appbtn_clicked(button: >k::Button) { - // Get button label. - let name = button.label().unwrap(); - let (binname, is_sudo) = if name == "CachyOS PackageInstaller" { - ("cachyos-pi-bin", true) - } else if name == "CachyOS Kernel Manager" { - ("cachyos-kernel-manager", false) - } else { - ("", false) - }; - - // Check if executable exists. - let exit_status = Exec::cmd("which").arg(binname).join().unwrap(); - if !exit_status.success() { - return; - } - - let mut envs = String::new(); - for env in glib::listenv() { - if env == "PATH" { - envs += "PATH=/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin "; - continue; - } - let _ = write!( - envs, - "{}=\"{}\" ", - env.to_str().unwrap(), - glib::getenv(&env).unwrap().to_str().unwrap() - ); - } - - // Get executable path. - let mut exe_path = - Exec::cmd("which").arg(binname).stdout(Redirection::Pipe).capture().unwrap().stdout_str(); - exe_path.pop(); - let bash_cmd = format!("{} {}", &envs, &exe_path); - - // Create context channel. - let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); - - // Spawn child process in separate thread. - std::thread::spawn(move || { - let exit_status = if is_sudo { - Exec::cmd("/sbin/pkexec").arg("bash").arg("-c").arg(bash_cmd).join().unwrap() - } else { - Exec::shell(bash_cmd).join().unwrap() - }; - tx.send(format!("Exit status successfully? = {:?}", exit_status.success())) - .expect("Couldn't send data to channel"); - }); - - rx.attach(None, move |text| { - println!("{}", text); - glib::Continue(true) - }); -} - fn on_btn_clicked(param: &[glib::Value]) -> Option { let widget = param[0].get::().unwrap(); let name = widget.widget_name(); diff --git a/src/pages.rs b/src/pages.rs new file mode 100644 index 0000000..26e2ad7 --- /dev/null +++ b/src/pages.rs @@ -0,0 +1,388 @@ +// extern crate gtk; + +use crate::application_browser::ApplicationBrowser; +use crate::data_types::*; +use gtk::{glib, Builder}; +use once_cell::sync::Lazy; +use std::fmt::Write as _; +use std::sync::Mutex; + +use gtk::prelude::*; + +use std::str; +use subprocess::{Exec, Redirection}; +// static mut g_app_browser: Lazy> = +// Lazy::new(|| Mutex::new(ApplicationBrowser::new())); + +// pub fn create_appbrowser_page() -> gtk::Box { +// NOTE: we might not even need that here +// let app_browser_box = ApplicationBrowser::default_impl().create_page(); +// let app_browser_box = ApplicationBrowser::default_impl().lock().expect("Initialization +// failed!").create_page(); + +// let app_browser_box = gtk::Box::new(gtk::Orientation::Vertical, 10); +// app_browser_box.set_expand(true); + +// let button_box = gtk::Box::new(gtk::Orientation::Horizontal, 10); +// let advanced_button = gtk::Button::with_label("advanced"); +// advanced_button.set_tooltip_text(Some("Toggle an extended selection of packages")); +// advanced_button.connect_clicked(on_advanced_clicked); +// let download_button = gtk::Button::with_label("download"); +// download_button.set_tooltip_text(Some("Download the most recent selection of packages")); +// download_button.connect_clicked(on_download_clicked); +// let reset_button = gtk::Button::with_label("reset"); +// reset_button.set_tooltip_text(Some("Reset your current selections...")); +// reset_button.connect_clicked(on_reload_clicked); +// let update_system_button = unsafe { g_app_browser.lock().unwrap().update_system_btn }; + +// Group filter +// let data = fs::read_to_string(format!("{}/data/application_utility/default.json", +// PKGDATADIR)) .expect("Unable to read file"); +// let groups: serde_json::Value = serde_json::from_str(&data).expect("Unable to parse"); +// let group_store = load_groups_data(&groups); +// let group_combo = utils::create_combo_with_model(&group_store); + +// Packing button box +// button_box.pack_start(&advanced_button, false, false, 10); +// button_box.pack_start(&group_combo, false, false, 10); +// button_box.pack_end(&update_system_button, false, false, 10); + +// button_box.pack_end(&reset_button, false, false, 10); +// button_box.pack_end(&download_button, false, false, 10); +// app_browser_box.pack_start(&button_box, false, false, 10); + +// create view and app store +// let (tree_view, app_store_size) = create_view_tree(&groups); + +// create a scrollable window +// let app_window = gtk::ScrolledWindow::new(gtk::Adjustment::NONE, gtk::Adjustment::NONE); +// app_window.set_vexpand(true); +// app_window.set_policy(gtk::PolicyType::Never, gtk::PolicyType::Automatic); +// add window to tree view +// app_window.add(&tree_view); + +// // setup grid +// let grid_inter = gtk::Grid::new(); +// grid_inter.set_column_homogeneous(true); +// grid_inter.set_row_homogeneous(true); +// // add grid to app browser +// app_browser_box.add(&grid_inter); +// grid_inter.attach(&app_window, 0, 0, 5, app_store_size as i32); +// +// app_browser_box +// gtk::Box::new(gtk::Orientation::Vertical, 10) +//} + +static mut g_local_units: Lazy> = Lazy::new(|| Mutex::new(SystemdUnits::new())); +static mut g_global_units: Lazy> = + Lazy::new(|| Mutex::new(SystemdUnits::new())); + +fn create_options_section() -> gtk::Box { + let topbox = gtk::Box::new(gtk::Orientation::Vertical, 2); + let box_collection = gtk::Box::new(gtk::Orientation::Horizontal, 10); + let label = gtk::Label::new(None); + label.set_line_wrap(true); + label.set_justify(gtk::Justification::Center); + label.set_text("Tweaks"); + + let psd_btn = gtk::CheckButton::with_label("Profile-sync-daemon enable"); + let systemd_oomd_btn = gtk::CheckButton::with_label("Systemd-oomd enabled"); + let apparmor_btn = gtk::CheckButton::with_label("Apparmor enabled"); + let ananicy_cpp_btn = gtk::CheckButton::with_label("Ananicy Cpp enabled"); + + unsafe { + psd_btn.set_data("actionData", "psd.service"); + psd_btn.set_data("actionType", "user_service"); + systemd_oomd_btn.set_data("actionData", "systemd-oomd.service"); + systemd_oomd_btn.set_data("actionType", "service"); + apparmor_btn.set_data("actionData", "apparmor.service"); + apparmor_btn.set_data("actionType", "service"); + ananicy_cpp_btn.set_data("actionData", "ananicy-cpp.service"); + ananicy_cpp_btn.set_data("actionType", "service"); + } + + for btn in &[&psd_btn, &systemd_oomd_btn, &apparmor_btn, &ananicy_cpp_btn] { + unsafe { + let data: &str = *btn.data("actionData").unwrap().as_ptr(); + if g_local_units.lock().unwrap().enabled_units.contains(&String::from(data)) + || g_global_units.lock().unwrap().enabled_units.contains(&String::from(data)) + { + btn.set_active(true); + } + } + } + + psd_btn.connect_clicked(on_servbtn_clicked); + systemd_oomd_btn.connect_clicked(on_servbtn_clicked); + apparmor_btn.connect_clicked(on_servbtn_clicked); + ananicy_cpp_btn.connect_clicked(on_servbtn_clicked); + + topbox.pack_start(&label, true, false, 1); + box_collection.pack_start(&psd_btn, true, false, 2); + box_collection.pack_start(&systemd_oomd_btn, true, false, 2); + box_collection.pack_start(&apparmor_btn, true, false, 2); + box_collection.pack_start(&ananicy_cpp_btn, true, false, 2); + box_collection.set_halign(gtk::Align::Fill); + topbox.pack_start(&box_collection, true, false, 1); + + topbox.set_hexpand(true); + topbox +} + +fn create_apps_section() -> gtk::Box { + let topbox = gtk::Box::new(gtk::Orientation::Vertical, 2); + let box_collection = gtk::Box::new(gtk::Orientation::Horizontal, 10); + let label = gtk::Label::new(None); + label.set_line_wrap(true); + label.set_justify(gtk::Justification::Center); + label.set_text("Applications"); + + let cachyos_pi = gtk::Button::with_label("CachyOS PackageInstaller"); + let cachyos_km = gtk::Button::with_label("CachyOS Kernel Manager"); + + cachyos_pi.connect_clicked(on_appbtn_clicked); + cachyos_km.connect_clicked(on_appbtn_clicked); + + box_collection.pack_start(&cachyos_pi, true, true, 2); + box_collection.pack_start(&cachyos_km, true, true, 2); + + topbox.pack_start(&label, true, true, 2); + + box_collection.set_halign(gtk::Align::Fill); + topbox.pack_start(&box_collection, true, true, 0); + + topbox.set_hexpand(true); + topbox +} + +fn load_enabled_units() { + unsafe { + g_local_units.lock().unwrap().loaded_units.clear(); + g_local_units.lock().unwrap().enabled_units.clear(); + + let mut exec_out = Exec::shell("systemctl list-unit-files -q --no-pager | tr -s \" \"") + .stdout(Redirection::Pipe) + .capture() + .unwrap() + .stdout_str(); + exec_out.pop(); + + let service_list = exec_out.split('\n'); + + for service in service_list { + let out: Vec<&str> = service.split(' ').collect(); + g_local_units.lock().unwrap().loaded_units.push(out[0].to_string()); + if out[1] == "enabled" { + g_local_units.lock().unwrap().enabled_units.push(out[0].to_string()); + } + } + } +} + +fn load_global_enabled_units() { + unsafe { + g_global_units.lock().unwrap().loaded_units.clear(); + g_global_units.lock().unwrap().enabled_units.clear(); + + let mut exec_out = + Exec::shell("systemctl --global list-unit-files -q --no-pager | tr -s \" \"") + .stdout(Redirection::Pipe) + .capture() + .unwrap() + .stdout_str(); + exec_out.pop(); + + let service_list = exec_out.split('\n'); + for service in service_list { + let out: Vec<&str> = service.split(' ').collect(); + g_global_units.lock().unwrap().loaded_units.push(out[0].to_string()); + if out[1] == "enabled" { + g_global_units.lock().unwrap().enabled_units.push(out[0].to_string()); + } + } + } +} + +pub fn create_tweaks_page(builder: &Builder) { + let install: gtk::Button = builder.object("tweaksBrowser").unwrap(); + install.set_visible(true); + + load_enabled_units(); + load_global_enabled_units(); + + let viewport = gtk::Viewport::new(gtk::Adjustment::NONE, gtk::Adjustment::NONE); + // let label = gtk::Label::new(None); + // label.set_line_wrap(true); + let image = gtk::Image::from_icon_name(Some("go-previous"), gtk::IconSize::Button); + let back_btn = gtk::Button::new(); + back_btn.set_image(Some(&image)); + back_btn.set_widget_name("home"); + + back_btn.connect_clicked(glib::clone!(@weak builder => move |button| { + let name = button.widget_name(); + let stack: gtk::Stack = builder.object("stack").unwrap(); + stack.set_visible_child_name(&format!("{}page", name)); + })); + + let options_section_box = create_options_section(); + let apps_section_box = create_apps_section(); + + let grid = gtk::Grid::new(); + grid.set_hexpand(true); + grid.set_margin_start(10); + grid.set_margin_end(10); + grid.set_margin_top(5); + grid.set_margin_bottom(5); + grid.attach(&back_btn, 0, 1, 1, 1); + let box_collection = gtk::Box::new(gtk::Orientation::Vertical, 5); + + box_collection.pack_start(&options_section_box, true, true, 5); + box_collection.pack_start(&apps_section_box, true, true, 5); + + box_collection.set_valign(gtk::Align::Center); + box_collection.set_halign(gtk::Align::Center); + grid.attach(&box_collection, 1, 2, 5, 1); + viewport.add(&grid); + viewport.show_all(); + + let stack: gtk::Stack = builder.object("stack").unwrap(); + let child_name = "tweaksBrowserpage"; + stack.add_named(&viewport, child_name); +} + +pub fn create_appbrowser_page(builder: &Builder) { + let install: gtk::Button = builder.object("appBrowser").unwrap(); + install.set_visible(true); + + let viewport = gtk::Viewport::new(gtk::Adjustment::NONE, gtk::Adjustment::NONE); + // let label = gtk::Label::new(None); + // label.set_line_wrap(true); + let image = gtk::Image::from_icon_name(Some("go-previous"), gtk::IconSize::Button); + let back_btn = gtk::Button::new(); + back_btn.set_image(Some(&image)); + back_btn.set_widget_name("home"); + + back_btn.connect_clicked(glib::clone!(@weak builder => move |button| { + let name = button.widget_name(); + let stack: gtk::Stack = builder.object("stack").unwrap(); + stack.set_visible_child_name(&format!("{}page", name)); + })); + + let grid = gtk::Grid::new(); + grid.set_hexpand(true); + grid.set_margin_start(10); + grid.set_margin_end(10); + grid.set_margin_top(5); + grid.set_margin_bottom(5); + grid.attach(&back_btn, 0, 1, 1, 1); + + let app_browser_ref = ApplicationBrowser::default_impl().lock().unwrap(); + let app_browser_box = app_browser_ref.get_page(); + grid.attach(app_browser_box, 0, 2, 1, 1); + + // Add grid to the viewport + // NOTE: we might eliminate that? + viewport.add(&grid); + viewport.show_all(); + + let stack: gtk::Stack = builder.object("stack").unwrap(); + let child_name = "appBrowserpage"; + stack.add_named(&viewport, child_name); +} + +fn on_servbtn_clicked(button: >k::CheckButton) { + // Get action data/type. + let action_type: &str; + let action_data: &str; + unsafe { + action_type = *button.data("actionType").unwrap().as_ptr(); + action_data = *button.data("actionData").unwrap().as_ptr(); + } + + let (user_only, pkexec_only) = + if action_type == "user_service" { ("--user", "--user $(logname)") } else { ("", "") }; + + let cmd: String; + unsafe { + let local_units = &g_local_units.lock().unwrap().enabled_units; + cmd = if !local_units.contains(&String::from(action_data)) { + format!( + "/sbin/pkexec {} bash -c \"systemctl {} enable --now --force {}\"", + pkexec_only, user_only, action_data + ) + } else { + format!( + "/sbin/pkexec {} bash -c \"systemctl {} disable --now {}\"", + pkexec_only, user_only, action_data + ) + }; + } + + // Spawn child process in separate thread. + std::thread::spawn(move || { + Exec::shell(cmd).join().unwrap(); + + if action_type == "user_service" { + load_global_enabled_units(); + } else { + load_enabled_units(); + } + }); +} + +fn on_appbtn_clicked(button: >k::Button) { + // Get button label. + let name = button.label().unwrap(); + let (binname, is_sudo) = if name == "CachyOS PackageInstaller" { + ("cachyos-pi-bin", true) + } else if name == "CachyOS Kernel Manager" { + ("cachyos-kernel-manager", false) + } else { + ("", false) + }; + + // Check if executable exists. + let exit_status = Exec::cmd("which").arg(binname).join().unwrap(); + if !exit_status.success() { + return; + } + + let mut envs = String::new(); + for env in glib::listenv() { + if env == "PATH" { + envs += "PATH=/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin "; + continue; + } + let _ = write!( + envs, + "{}=\"{}\" ", + env.to_str().unwrap(), + glib::getenv(&env).unwrap().to_str().unwrap() + ); + } + + // Get executable path. + let mut exe_path = + Exec::cmd("which").arg(binname).stdout(Redirection::Pipe).capture().unwrap().stdout_str(); + exe_path.pop(); + let bash_cmd = format!("{} {}", &envs, &exe_path); + + // Create context channel. + let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); + + // Spawn child process in separate thread. + std::thread::spawn(move || { + let exit_status = if is_sudo { + Exec::cmd("/sbin/pkexec").arg("bash").arg("-c").arg(bash_cmd).join().unwrap() + } else { + Exec::shell(bash_cmd).join().unwrap() + }; + tx.send(format!("Exit status successfully? = {:?}", exit_status.success())) + .expect("Couldn't send data to channel"); + }); + + rx.attach(None, move |text| { + println!("{}", text); + glib::Continue(true) + }); +} diff --git a/src/utils.rs b/src/utils.rs index 7019d86..0f09a80 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,4 @@ +use gtk::prelude::*; use std::fs::File; use std::{fs, slice, str}; @@ -54,6 +55,16 @@ pub fn check_regular_file(path: &str) -> bool { } } +pub fn create_combo_with_model(group_store: >k::ListStore) -> gtk::ComboBox { + let group_combo = gtk::ComboBox::with_model(group_store); + let combo_renderer = gtk::CellRendererText::new(); + group_combo.pack_start(&combo_renderer, true); + group_combo.add_attribute(&combo_renderer, "text", 0); + group_combo.set_active(Some(0)); + + group_combo +} + #[cfg(test)] mod test { use super::*; diff --git a/ui/cachyos-hello.glade b/ui/cachyos-hello.glade index fb6b6f8..20d2e7f 100644 --- a/ui/cachyos-hello.glade +++ b/ui/cachyos-hello.glade @@ -425,10 +425,27 @@ We, the CachyOS Developers, hope that you will enjoy using CachyOS as much as we True - Apps/Tweaks + Install Apps appBrowser False True + Install Apps + 15 + 15 + + + + True + True + 0 + + + + + Apps/Tweaks + tweaksBrowser + False + True Apps/Tweaks 15 15