commit f0cb465c858a313849d19bc7a749bd10af6250a7 Author: lily Date: Tue Apr 15 22:01:07 2025 -0400 add: base project diff --git a/.direnv b/.direnv new file mode 100644 index 0000000..65326bb --- /dev/null +++ b/.direnv @@ -0,0 +1 @@ +use nix \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f0330d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +build/ +.vscode/ +debug/ +release/ +.qmake.stash +Makefile +Makefile.Debug +Makefile.Release \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8ba2b7d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.10) +project(waydock CXX) +include_directories(inc) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + + +file(GLOB_RECURSE SOURCES src/*.cpp) +file(GLOB_RECURSE UI_SRC ui/*.ui) +find_package(Qt6 REQUIRED COMPONENTS Widgets Gui Core) +qt_add_executable(waydock ${SOURCES} ${UI_SRC}) +qt_standard_project_setup() + +target_link_libraries(waydock PRIVATE Widget Gui Core) + +set_target_properties(waydock PROPERTIES + AUTOMOC ON + AUTOUIC ON + AUTORCC ON +) + +if(WIN32) + set_target_properties(waydock PROPERTIES WIN32_EXECUTABLE ON) +endif() + +if (MSVC) + target_compile_options(waydock PRIVATE "-Zc:__cplusplus" "-permissive-") +endif() + +if (CMAKE_BUILD_TYPE MATCHES "^[Dd]ebug") + message(STATUS "Debug mode enabled") + # target_compile_options(waydock PRIVATE -fsanitize=address -fno-omit-frame-pointer) + # target_link_options(waydock PRIVATE -fsanitize=address) +endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..0da2ae9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contributing + +To be written. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..51ae63a --- /dev/null +++ b/LICENSE @@ -0,0 +1,360 @@ +# GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +## Preamble + +The licenses for most software are designed to take away your freedom +to share and change it. By contrast, the GNU General Public License is +intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + +We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, +we want its recipients to know that what they have is not the +original, so that any problems introduced by others will not reflect +on the original authors' reputations. + +Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at +all. + +The precise terms and conditions for copying, distribution and +modification follow. + +## TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +**0.** This License applies to any program or other work which +contains a notice placed by the copyright holder saying it may be +distributed under the terms of this General Public License. The +"Program", below, refers to any such program or work, and a "work +based on the Program" means either the Program or any derivative work +under copyright law: that is to say, a work containing the Program or +a portion of it, either verbatim or with modifications and/or +translated into another language. (Hereinafter, translation is +included without limitation in the term "modification".) Each licensee +is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the Program +(independent of having been made by running the Program). Whether that +is true depends on what the Program does. + +**1.** You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a +fee. + +**2.** You may modify your copy or copies of the Program or any +portion of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + +**a)** You must cause the modified files to carry prominent notices +stating that you changed the files and the date of any change. + + +**b)** You must cause any work that you distribute or publish, that in +whole or in part contains or is derived from the Program or any part +thereof, to be licensed as a whole at no charge to all third parties +under the terms of this License. + + +**c)** If the modified program normally reads commands interactively +when run, you must cause it, when started running for such interactive +use in the most ordinary way, to print or display an announcement +including an appropriate copyright notice and a notice that there is +no warranty (or else, saying that you provide a warranty) and that +users may redistribute the program under these conditions, and telling +the user how to view a copy of this License. (Exception: if the +Program itself is interactive but does not normally print such an +announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +**3.** You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + +**a)** Accompany it with the complete corresponding machine-readable +source code, which must be distributed under the terms of Sections 1 +and 2 above on a medium customarily used for software interchange; or, + + +**b)** Accompany it with a written offer, valid for at least three +years, to give any third party, for a charge no more than your cost of +physically performing source distribution, a complete machine-readable +copy of the corresponding source code, to be distributed under the +terms of Sections 1 and 2 above on a medium customarily used for +software interchange; or, + + +**c)** Accompany it with the information you received as to the offer +to distribute corresponding source code. (This alternative is allowed +only for noncommercial distribution and only if you received the +program in object code or executable form with such an offer, in +accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + +**4.** You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt otherwise +to copy, modify, sublicense or distribute the Program is void, and +will automatically terminate your rights under this License. However, +parties who have received copies, or rights, from you under this +License will not have their licenses terminated so long as such +parties remain in full compliance. + +**5.** You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +**6.** Each time you redistribute the Program (or any work based on +the Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + +**7.** If, as a consequence of a court judgment or allegation of +patent infringement or for any other reason (not limited to patent +issues), conditions are imposed on you (whether by court order, +agreement or otherwise) that contradict the conditions of this +License, they do not excuse you from the conditions of this License. +If you cannot distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, +then as a consequence you may not distribute the Program at all. For +example, if a patent license would not permit royalty-free +redistribution of the Program by all those who receive copies directly +or indirectly through you, then the only way you could satisfy both it +and this License would be to refrain entirely from distribution of the +Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +**8.** If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + +**9.** The Free Software Foundation may publish revised and/or new +versions of the General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Program does not specify a +version number of this License, you may choose any version ever +published by the Free Software Foundation. + +**10.** If you wish to incorporate parts of the Program into other +free programs whose distribution conditions are different, write to +the author to ask for permission. For software which is copyrighted by +the Free Software Foundation, write to the Free Software Foundation; +we sometimes make exceptions for this. Our decision will be guided by +the two goals of preserving the free status of all derivatives of our +free software and of promoting the sharing and reuse of software +generally. + +**NO WARRANTY** + +**11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +**12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + +END OF TERMS AND CONDITIONS + +## How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these +terms. + +To do so, attach the following notices to the program. It is safest to +attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + one line to give the program's name and an idea of what it does. + Copyright (C) yyyy name of author + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, see . + +Also add information on how to contact you by electronic and paper +mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details + type `show w'. This is free software, and you are welcome + to redistribute it under certain conditions; type `show c' + for details. + +The hypothetical commands \`show w' and \`show c' should show the +appropriate parts of the General Public License. Of course, the +commands you use may be called something other than \`show w' and +\`show c'; they could even be mouse-clicks or menu items--whatever +suits your program. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the program, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright + interest in the program `Gnomovision' + (which makes passes at compilers) written + by James Hacker. + + signature of Moe Ghoul, 1 April 1989 + Moe Ghoul, President of Vice + +This General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, +you may consider it more useful to permit linking proprietary +applications with the library. If this is what you want to do, use the +[GNU Lesser General Public +License](https://www.gnu.org/licenses/lgpl.html) instead of this +License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..395a1ad --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ + + +
+

🍭 Waydock

+

Universal dock for wayland compositors

+ C++ + Linux +
+
+Waydock is a univeral dock for wayland compositors, utilizing Qt, with a high-level of customization available. + +## Installing + +🛠️ TBW + +## Configuration + +🛠️ TBW + +## Code of Conduct and Contributing + +- See [CODE_OF_CONDUCT.md](https://git.lilyvex.dev/nonsensical-dev/governance/-/blob/main/CODE_OF_CONDUCT.md) +- See [CONTRIBUTING.md](CONTRIBUTING.md) diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..c8eb41f --- /dev/null +++ b/flake.nix @@ -0,0 +1,24 @@ +{ + description = "Universal dock for wayland compositors" + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = inputs@{ flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + systems = [ + "x86_64-linux" "aarch64-linux" + ]; + + perSystem = { config, self', inputs', pkgs, system, ... }: { + devShells.default = pkgs.mkShell { + packages = with pkgs; [ + cmake + llvmPackages_19.clangUseLLVM + qt6.full + ]; + }; + }; + }; +} \ No newline at end of file diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..580b477 --- /dev/null +++ b/src/config.h @@ -0,0 +1,18 @@ +#include +#include + +#include "ini.h" +#include "xdg.h" + +class Config { + private: + struct config { + std::optional configDir; + + config() {} + ~config() {} + }; + public: + Config(); + ~Config(); +}; \ No newline at end of file diff --git a/src/dock.cpp b/src/dock.cpp new file mode 100644 index 0000000..0605350 --- /dev/null +++ b/src/dock.cpp @@ -0,0 +1,47 @@ +#include "dock.h" + +/// @brief Create dock windows on all screens +/// @param app Qt Application callback +void Dock::createDockWindows(QApplication* app) { + for (int i = 0; i < this->m_screens.size(); ++i) { + QScreen* screen = this->m_screens[i]; + + qDebug() << "Creating dock window for screen:" << screen->name(); + + QWidget* container = new QWidget(nullptr, Qt::FramelessWindowHint | Qt::Tool); + container->setAttribute(Qt::WA_TranslucentBackground); + container->setAttribute(Qt::WA_NoSystemBackground); + container->setWindowFlag(Qt::WindowStaysOnTopHint); + container->resize(screen->geometry().size().width() / 2, 60); + + QLabel* label = new QLabel(QString("✨ Dock %1 ✨").arg(i)); + label->setAlignment(Qt::AlignCenter); + label->setStyleSheet(R"( + QLabel { + color: white; + font-size: 18px; + background-color: rgba(0, 0, 0, 180); + border-radius: 10px; + } + )"); + + QVBoxLayout* layout = new QVBoxLayout(container); + layout->addWidget(label); + layout->setContentsMargins(0, 0, 0, 0); + + QRect screenRect = screen->geometry(); + int x = screenRect.center().x() - container->width() / 2; + int y = screenRect.bottom() - container->height(); + container->move(x, y); + + container->show(); + } + + app->exec(); +} + +Dock::Dock(QApplication* app) { + this->createDockWindows(app); +} + +Dock::~Dock() {}; \ No newline at end of file diff --git a/src/dock.h b/src/dock.h new file mode 100644 index 0000000..3a48883 --- /dev/null +++ b/src/dock.h @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +class Dock { + private: + /// @brief Stores all dock windows + QList m_dockWindows; + + /// @brief QWidget container for dock windows + QList m_dockWindowContainters; + + /// @brief Stores all configuration windows + QList m_configWindows; + + /// @brief Stores all available screens + QList m_screens = QGuiApplication::screens(); + + /// @brief An application in the dock + struct dock_application { + /// @brief Whether the application is pinned to the dock + bool pinned; + + /// @brief Index of where the application is pinned (0 when disabled) + int pinIndex; + + /// @brief Whether the application is running + bool running; + + /// @brief Path to the application's desktop file entry + std::filesystem::path desktopFilePath; + + /// @brief Reference the to the icon file of the application + std::ifstream desktopIcon; + + /// @brief Name of the application as specified by the desktop file + std::string desktopName; + + /// @brief Name of the running process, used if an application desktop file does not exist + std::string processName; + + /// @brief Create a new `dock_application` with an optional desktop file + /// @param filename Optional desktop file for metadata + dock_application() { + getDesktopName(); + getDesktopIcon(); + } + + /// @brief Clean up `dock_application` resources + ~dock_application() { + if (desktopIcon.is_open()) { + desktopIcon.close(); + } + } + + /// @brief Get application desktop name from desktop file + /// @return UTF-16 desktop name + std::optional getDesktopName() { + inih::INIReader r; + std::string name; + + try { + r = std::move(inih::INIReader(desktopFilePath.string())); + } catch (std::runtime_error&) { + return std::nullopt; + } + + try { + name = r.Get("Desktop Entry", "name"); + } catch (std::runtime_error&) { + return std::nullopt; + } + + return name; + } + + /// @brief Get application desktop icon from desktop file + /// @return Desktop icon file stream + std::optional getDesktopIcon() { + inih::INIReader r; + std::filesystem::path icon_path; + std::ifstream icon{icon_path}; + + try { + r = std::move(inih::INIReader(desktopFilePath.string())); + } catch (std::runtime_error&) { + return std::nullopt; + } + + try { + icon_path = r.Get("Desktop Entry", "icon"); + } catch (std::runtime_error&) { + return std::nullopt; + } + + return icon; + } + }; + + /// @brief List of all applications in the dock + std::vector m_dockApplications; + + /// @brief Create dock windows on all screens + /// @param app Qt Application callback + void createDockWindows(QApplication* app); + public: + Dock(QApplication* app); + ~Dock(); +}; \ No newline at end of file diff --git a/src/ini.h b/src/ini.h new file mode 100644 index 0000000..29bc3a6 --- /dev/null +++ b/src/ini.h @@ -0,0 +1,624 @@ +/** + * Yet another .ini parser for modern c++ (made for cpp17), inspired and extend + * from @benhoyt's inih. See project page: https://github.com/SSARCandy/ini-cpp + */ + +#ifndef __INI_H__ +#define __INI_H__ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace inih { + +/* Typedef for prototype of handler function. */ +typedef int (*ini_handler)(void* user, const char* section, const char* name, + const char* value); + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +#define INI_STOP_ON_FIRST_ERROR 1 +#define INI_MAX_LINE 2000 +#define INI_INITIAL_ALLOC 200 +#define MAX_SECTION 50 +#define MAX_NAME 50 +#define INI_START_COMMENT_PREFIXES ";#" +#define INI_INLINE_COMMENT_PREFIXES ";" + +/* Strip whitespace chars off end of given string, in place. Return s. */ +inline static char* rstrip(char* s) { + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +inline static char* lskip(const char* s) { + while (*s && isspace((unsigned char)(*s))) s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to null at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +inline static char* find_chars_or_comment(const char* s, const char* chars) { + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } + return (char*)s; +} + +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +inline static char* strncpy0(char* dest, const char* src, size_t size) { + strncpy(dest, src, size - 1); + dest[size - 1] = '\0'; + return dest; +} + +/* See documentation in header file. */ +inline int ini_parse_stream(ini_reader reader, void* stream, + ini_handler handler, void* user) { + /* Uses a fair bit of stack (use heap instead if you need to) */ + char* line; + size_t max_line = INI_INITIAL_ALLOC; + char* new_line; + size_t offset; + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + + line = (char*)malloc(INI_INITIAL_ALLOC); + if (!line) { + return -2; + } + +#if INI_HANDLER_LINENO +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) +#else +#define HANDLER(u, s, n, v) handler(u, s, n, v) +#endif + + /* Scan through stream line by line */ + while (reader(line, (int)max_line, stream) != NULL) { + offset = strlen(line); + while (offset == max_line - 1 && line[offset - 1] != '\n') { + max_line *= 2; + if (max_line > INI_MAX_LINE) max_line = INI_MAX_LINE; + new_line = (char*)realloc(line, max_line); + if (!new_line) { + free(line); + return -2; + } + line = new_line; + if (reader(line + offset, (int)(max_line - offset), stream) == NULL) + break; + if (max_line >= INI_MAX_LINE) break; + offset += strlen(line + offset); + } + + lineno++; + + start = line; + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } + start = lskip(rstrip(start)); + + if (strchr(INI_START_COMMENT_PREFIXES, *start)) { + /* Start-of-line comment */ + } else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = end + 1; + end = find_chars_or_comment(value, NULL); + if (*end) *end = '\0'; + value = lskip(value); + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!HANDLER(user, section, name, value) && !error) + error = lineno; + } else if (!error) { + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } + } + + if (error) break; + } + + free(line); + + return error; +} + +inline int ini_parse_file(FILE* file, ini_handler handler, void* user) { + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +inline int ini_parse(const char* filename, ini_handler handler, void* user) { + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} + +#endif /* __INI_H__ */ + +#ifndef __INIREADER_H__ +#define __INIREADER_H__ + +// Read an INI file into easy-to-access name/value pairs. (Note that I've gone +// for simplicity here rather than speed, but it should be pretty decent.) +class INIReader { + public: + // Empty Constructor + INIReader(){}; + + // Construct INIReader and parse given filename. See ini.h for more info + // about the parsing. + INIReader(std::string filename); + + // Construct INIReader and parse given file. See ini.h for more info + // about the parsing. + INIReader(FILE* file); + + // Return the result of ini_parse(), i.e., 0 on success, line number of + // first error on parse error, or -1 on file open error. + int ParseError() const; + + // Return the list of sections found in ini file + const std::set Sections() const; + + // Return the list of keys in the given section + const std::set Keys(std::string section) const; + + const std::unordered_map Get( + std::string section) const; + + template + T Get(const std::string& section, const std::string& name) const; + + template + T Get(const std::string& section, const std::string& name, + T&& default_v) const; + + template + std::vector GetVector(const std::string& section, + const std::string& name) const; + + template + std::vector GetVector(const std::string& section, + const std::string& name, + const std::vector& default_v) const; + + template + void InsertEntry(const std::string& section, const std::string& name, + const T& v); + + template + void InsertEntry(const std::string& section, const std::string& name, + const std::vector& vs); + + template + void UpdateEntry(const std::string& section, const std::string& name, + const T& v); + + template + void UpdateEntry(const std::string& section, const std::string& name, + const std::vector& vs); + + protected: + int _error; + std::unordered_map> + _values; + static int ValueHandler(void* user, const char* section, const char* name, + const char* value); + + template + T Converter(const std::string& s) const; + + const bool BoolConverter(std::string s) const; + + template + std::string V2String(const T& v) const; + + template + std::string Vec2String(const std::vector& v) const; +}; + +#endif // __INIREADER_H__ + +#ifndef __INIREADER__ +#define __INIREADER__ + +/** + * @brief Construct an INIReader object from a file name + * @param filename The name of the INI file to parse + * @throws std::runtime_error if there is an error parsing the INI file + */ +inline INIReader::INIReader(std::string filename) { + _error = ini_parse(filename.c_str(), ValueHandler, this); + ParseError(); +} + +/** + * @brief Construct an INIReader object from a file pointer + * @param file A pointer to the INI file to parse + * @throws std::runtime_error if there is an error parsing the INI file + */ +inline INIReader::INIReader(FILE* file) { + _error = ini_parse_file(file, ValueHandler, this); + ParseError(); +} + +inline int INIReader::ParseError() const { + switch (_error) { + case 0: + break; + case -1: + throw std::runtime_error("ini file not found."); + case -2: + throw std::runtime_error("memory alloc error"); + default: + throw std::runtime_error("parse error on line no: " + + std::to_string(_error)); + } + return 0; +} + +/** + * @brief Return the list of sections found in ini file + * @return The list of sections found in ini file + */ +inline const std::set INIReader::Sections() const { + std::set retval; + for (auto const& element : _values) { + retval.insert(element.first); + } + return retval; +} + +/** + * @brief Return the list of keys in the given section + * @param section The section name + * @return The list of keys in the given section + */ +inline const std::set INIReader::Keys(std::string section) const { + auto const _section = Get(section); + std::set retval; + for (auto const& element : _section) { + retval.insert(element.first); + } + return retval; +} + +/** + * @brief Get the map representing the values in a section of the INI file + * @param section The name of the section to retrieve + * @return The map representing the values in the given section + * @throws std::runtime_error if the section is not found + */ +inline const std::unordered_map INIReader::Get( + std::string section) const { + auto const _section = _values.find(section); + if (_section == _values.end()) { + throw std::runtime_error("section '" + section + "' not found."); + } + return _section->second; +} + +/** + * @brief Return the value of the given key in the given section + * @param section The section name + * @param name The key name + * @return The value of the given key in the given section + */ +template +inline T INIReader::Get(const std::string& section, + const std::string& name) const { + auto const _section = Get(section); + auto const _value = _section.find(name); + + if (_value == _section.end()) { + throw std::runtime_error("key '" + name + "' not found in section '" + + section + "'."); + } + + std::string value = _value->second; + + if constexpr (std::is_same()) { + return value; + } else if constexpr (std::is_same()) { + return BoolConverter(value); + } else { + return Converter(value); + }; +} + +/** + * @brief Return the value of the given key in the given section, return default + * if not found + * @param section The section name + * @param name The key name + * @param default_v The default value + * @return The value of the given key in the given section, return default if + * not found + */ +template +inline T INIReader::Get(const std::string& section, const std::string& name, + T&& default_v) const { + try { + return Get(section, name); + } catch (std::runtime_error&) { + return default_v; + } +} + +/** + * @brief Return the value array of the given key in the given section. + * @param section The section name + * @param name The key name + * @return The value array of the given key in the given section. + * + * For example: + * ```ini + * [section] + * key = 1 2 3 4 + * ``` + * ```cpp + * const auto vs = ini.GetVector>("section", "key"); + * // vs = {1, 2, 3, 4} + * ``` + */ +template +inline std::vector INIReader::GetVector(const std::string& section, + const std::string& name) const { + std::string value = Get(section, name); + + std::istringstream out{value}; + const std::vector strs{std::istream_iterator{out}, + std::istream_iterator()}; + try { + std::vector vs{}; + for (const std::string& s : strs) { + vs.emplace_back(Converter(s)); + } + return vs; + } catch (std::exception&) { + throw std::runtime_error("cannot parse value " + value + + " to vector."); + } +} + +/** + * @brief Return the value array of the given key in the given section, return + * default if not found + * @param section The section name + * @param name The key name + * @param default_v The default value + * @return The value array of the given key in the given section, return default + * if not found + * + * @see INIReader::GetVector + */ +template +inline std::vector INIReader::GetVector( + const std::string& section, const std::string& name, + const std::vector& default_v) const { + try { + return GetVector(section, name); + } catch (std::runtime_error&) { + return default_v; + }; +} + +/** + * @brief Insert a key-value pair into the INI file + * @param section The section name + * @param name The key name + * @param v The value to insert + * @throws std::runtime_error if the key already exists in the section + */ +template +inline void INIReader::InsertEntry(const std::string& section, + const std::string& name, const T& v) { + if (_values[section][name].size() > 0) { + throw std::runtime_error("duplicate key '" + std::string(name) + + "' in section '" + section + "'."); + } + _values[section][name] = V2String(v); +} + +/** + * @brief Insert a vector of values into the INI file + * @param section The section name + * @param name The key name + * @param vs The vector of values to insert + * @throws std::runtime_error if the key already exists in the section + */ +template +inline void INIReader::InsertEntry(const std::string& section, + const std::string& name, + const std::vector& vs) { + if (_values[section][name].size() > 0) { + throw std::runtime_error("duplicate key '" + std::string(name) + + "' in section '" + section + "'."); + } + _values[section][name] = Vec2String(vs); +} + +/** + * @brief Update a key-value pair in the INI file + * @param section The section name + * @param name The key name + * @param v The new value to set + * @throws std::runtime_error if the key does not exist in the section + */ +template +inline void INIReader::UpdateEntry(const std::string& section, + const std::string& name, const T& v) { + if (!_values[section][name].size()) { + throw std::runtime_error("key '" + std::string(name) + + "' not exist in section '" + section + "'."); + } + _values[section][name] = V2String(v); +} + +/** + * @brief Update a vector of values in the INI file + * @param section The section name + * @param name The key name + * @param vs The new vector of values to set + * @throws std::runtime_error if the key does not exist in the section + */ +template +inline void INIReader::UpdateEntry(const std::string& section, + const std::string& name, + const std::vector& vs) { + if (!_values[section][name].size()) { + throw std::runtime_error("key '" + std::string(name) + + "' not exist in section '" + section + "'."); + } + _values[section][name] = Vec2String(vs); +} + +template +inline std::string INIReader::V2String(const T& v) const { + std::stringstream ss; + ss << v; + return ss.str(); +} + +template +inline std::string INIReader::Vec2String(const std::vector& v) const { + if (v.empty()) { + return ""; + } + std::ostringstream oss; + std::copy(v.begin(), v.end() - 1, std::ostream_iterator(oss, " ")); + oss << v.back(); + + return oss.str(); +} + +template +inline T INIReader::Converter(const std::string& s) const { + try { + T v{}; + std::istringstream _{s}; + _.exceptions(std::ios::failbit); + _ >> v; + return v; + } catch (std::exception&) { + throw std::runtime_error("cannot parse value '" + s + "' to type."); + }; +} + +inline const bool INIReader::BoolConverter(std::string s) const { + std::transform(s.begin(), s.end(), s.begin(), ::tolower); + static const std::unordered_map s2b{ + {"1", true}, {"true", true}, {"yes", true}, {"on", true}, + {"0", false}, {"false", false}, {"no", false}, {"off", false}, + }; + auto const value = s2b.find(s); + if (value == s2b.end()) { + throw std::runtime_error("'" + s + "' is not a valid boolean value."); + } + return value->second; +} + +inline int INIReader::ValueHandler(void* user, const char* section, + const char* name, const char* value) { + INIReader* reader = (INIReader*)user; + if (reader->_values[section][name].size() > 0) { + throw std::runtime_error("duplicate key '" + std::string(name) + + "' in section '" + section + "'."); + } + reader->_values[section][name] = value; + return 1; +} +#endif // __INIREADER__ + +#ifndef __INIWRITER_H__ +#define __INIWRITER_H__ + +class INIWriter { + public: + INIWriter(){}; + /** + * @brief Write the contents of an INI file to a new file + * @param filepath The path of the output file + * @param reader The INIReader object to write to the file + * @throws std::runtime_error if the output file already exists or cannot be + * opened + */ + inline static void write(const std::string& filepath, + const INIReader& reader) { + if (struct stat buf; stat(filepath.c_str(), &buf) == 0) { + throw std::runtime_error("file: " + filepath + " already exist."); + } + std::ofstream out; + out.open(filepath); + if (!out.is_open()) { + throw std::runtime_error("cannot open output file: " + filepath); + } + for (const auto& section : reader.Sections()) { + out << "[" << section << "]\n"; + for (const auto& key : reader.Keys(section)) { + out << key << "=" << reader.Get(section, key) << "\n"; + } + } + out.close(); + } +}; +} +#endif /* __INIWRITER_H__ */ \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..68b30bd --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "dock.h" + +void customMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + // static QFile logFile("debug_output.log"); + // if (!logFile.isOpen()) { + // logFile.open(QIODevice::Append | QIODevice::Text); + // } + + // QTextStream out(&logFile); + QTextStream console(stdout); + + QString levelStr; + QString colorStart; + const QString colorEnd = "\033[0m"; + + switch (type) { + case QtDebugMsg: + levelStr = "DEBUG"; + colorStart = "\033[36m"; // Cyan + break; + case QtInfoMsg: + levelStr = "INFO"; + colorStart = "\033[32m"; // Green + break; + case QtWarningMsg: + levelStr = "WARNING"; + colorStart = "\033[33m"; // Yellow + break; + case QtCriticalMsg: + levelStr = "CRITICAL"; + colorStart = "\033[31m"; // Red + break; + case QtFatalMsg: + levelStr = "FATAL"; + colorStart = "\033[41m\033[97m"; // Red background, white text + break; + } + + QString time = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz"); + + // Plain version for log file + QString plainText = QString("[%1] [%2] %3 (%4:%5)") + .arg(time, levelStr, msg, context.file, QString::number(context.line)); + + // Colored level only for console + QString coloredText = QString("[%1] [%2%3%4] %5 (%6:%7)") + .arg(time, colorStart, levelStr, colorEnd, msg, context.file, QString::number(context.line)); + + // out << plainText << "\n"; + // out.flush(); + + console << coloredText << "\n"; + console.flush(); + + if (type == QtFatalMsg) + abort(); +} + +int main(int argc, char *argv[]) { + qInstallMessageHandler(customMessageHandler); + + QApplication app(argc, argv); + Dock dock{&app}; + + return 0; +} \ No newline at end of file diff --git a/src/xdg.h b/src/xdg.h new file mode 100644 index 0000000..df88c02 --- /dev/null +++ b/src/xdg.h @@ -0,0 +1,217 @@ +/* + * Copyright © 2020 Danilo Spinella + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + #pragma once + + #include + #include + #include + #include + #include + #include + + #include + + namespace xdg { + + class BaseDirectoryException : public std::exception { + public: + explicit BaseDirectoryException(std::string msg) : msg_(std::move(msg)) {} + + [[nodiscard]] auto what() const noexcept -> const char * override { + return msg_.c_str(); + } + [[nodiscard]] auto msg() const noexcept -> std::string { return msg_; } + + private: + const std::string msg_; + }; + + class BaseDirectories { + public: + BaseDirectories() { + const char *home_env = getenv("HOME"); + if (home_env == nullptr) { + throw BaseDirectoryException("$HOME must be set"); + } + home_ = std::filesystem::path{home_env}; + + data_home_ = GetAbsolutePathFromEnvOrDefault( + "XDG_DATA_HOME", home_ / ".local" / "share"); + config_home_ = GetAbsolutePathFromEnvOrDefault("XDG_CONFIG_HOME", + home_ / ".config"); + data_ = GetPathsFromEnvOrDefault("XDG_DATA_DIRS", + std::vector{ + "/usr/local/share", "/usr/share"}); + config_ = GetPathsFromEnvOrDefault( + "XDG_CONFIG_DIRS", std::vector{"/etc/xdg"}); + cache_home_ = + GetAbsolutePathFromEnvOrDefault("XDG_CACHE_HOME", home_ / ".cache"); + + SetRuntimeDir(); + } + + static auto GetInstance() -> BaseDirectories & { + static BaseDirectories dirs; + + return dirs; + } + + [[nodiscard]] auto DataHome() -> const std::filesystem::path & { + return data_home_; + } + [[nodiscard]] auto ConfigHome() -> const std::filesystem::path & { + return config_home_; + } + [[nodiscard]] auto Data() -> const std::vector & { + return data_; + } + [[nodiscard]] auto Config() -> const std::vector & { + return config_; + } + [[nodiscard]] auto CacheHome() -> const std::filesystem::path & { + return cache_home_; + } + [[nodiscard]] auto Runtime() + -> const std::optional & { + return runtime_; + } + + private: + void SetRuntimeDir() { + const char *runtime_env = getenv("XDG_RUNTIME_DIR"); + if (runtime_env != nullptr) { + std::filesystem::path runtime_dir{runtime_env}; + if (runtime_dir.is_absolute()) { + if (!std::filesystem::exists(runtime_dir)) { + throw BaseDirectoryException( + "$XDG_RUNTIME_DIR must exist on the system"); + } + + auto runtime_dir_perms = + std::filesystem::status(runtime_dir).permissions(); + using perms = std::filesystem::perms; + // Check XDG_RUNTIME_DIR permissions are 0700 + if (((runtime_dir_perms & perms::owner_all) == perms::none) || + ((runtime_dir_perms & perms::group_all) != perms::none) || + ((runtime_dir_perms & perms::others_all) != perms::none)) { + throw BaseDirectoryException( + "$XDG_RUNTIME_DIR must have 0700 as permissions"); + } + runtime_.emplace(runtime_dir); + } + } + } + + static auto + GetAbsolutePathFromEnvOrDefault(const char *env_name, + std::filesystem::path &&default_path) + -> std::filesystem::path { + const char *env_var = getenv(env_name); + if (env_var == nullptr) { + return std::move(default_path); + } + auto path = std::filesystem::path{env_var}; + if (!path.is_absolute()) { + return std::move(default_path); + } + + return path; + } + + static auto + GetPathsFromEnvOrDefault(const char *env_name, + std::vector &&default_paths) + -> std::vector { + auto *env = getenv(env_name); + if (env == nullptr) { + return std::move(default_paths); + } + std::string paths{env}; + + std::vector dirs{}; + size_t start = 0; + size_t pos = 0; + while ((pos = paths.find_first_of(':', start)) != std::string::npos) { + std::filesystem::path current_path{ + paths.substr(start, pos - start)}; + if (current_path.is_absolute() && + !VectorContainsPath(dirs, current_path)) { + dirs.emplace_back(current_path); + } + start = pos + 1; + } + std::filesystem::path current_path{paths.substr(start, pos - start)}; + if (current_path.is_absolute() && + !VectorContainsPath(dirs, current_path)) { + dirs.emplace_back(current_path); + } + + if (dirs.empty()) { + return std::move(default_paths); + } + + return dirs; + } + + static auto + VectorContainsPath(const std::vector &paths, + const std::filesystem::path &path) -> bool { + return std::find(std::begin(paths), std::end(paths), path) != + paths.end(); + } + + std::filesystem::path home_; + + std::filesystem::path data_home_; + std::filesystem::path config_home_; + std::vector data_; + std::vector config_; + std::filesystem::path cache_home_; + std::optional runtime_; + + }; // namespace xdg + + [[nodiscard]] inline auto DataHomeDir() -> const std::filesystem::path & { + return BaseDirectories::GetInstance().DataHome(); + } + [[nodiscard]] inline auto ConfigHomeDir() -> const std::filesystem::path & { + return BaseDirectories::GetInstance().ConfigHome(); + } + [[nodiscard]] inline auto DataDirs() + -> const std::vector & { + return BaseDirectories::GetInstance().Data(); + } + [[nodiscard]] inline auto ConfigDirs() + -> const std::vector & { + return BaseDirectories::GetInstance().Config(); + } + [[nodiscard]] inline auto CacheHomeDir() -> const std::filesystem::path & { + return BaseDirectories::GetInstance().CacheHome(); + } + [[nodiscard]] inline auto RuntimeDir() + -> const std::optional & { + return BaseDirectories::GetInstance().Runtime(); + } + + } // namespace xdg + \ No newline at end of file diff --git a/waydock.pro b/waydock.pro new file mode 100644 index 0000000..cc7ab1a --- /dev/null +++ b/waydock.pro @@ -0,0 +1,12 @@ +QT += core gui widgets + +CONFIG += c++17 debug console +TEMPLATE = app +TARGET = waydock + +SOURCES += \ + src/main.cpp \ + src/dock.cpp \ + +# HEADERS += \ +# src/some_header.h \ No newline at end of file