39using IntegrationItem = ns_db::ns_desktop::IntegrationItem;
45constexpr std::string_view
const template_dir_mimetype =
"icons/hicolor/{}x{}/mimetypes";
46constexpr std::string_view
const template_file_mime =
"application-flatimage_{}.png";
48constexpr std::string_view
const template_dir_apps =
"icons/hicolor/{}x{}/apps";
49constexpr std::string_view
const template_file_app =
"flatimage_{}.png";
51constexpr std::string_view
const template_dir_mime_scalable =
"icons/hicolor/scalable/mimetypes";
52constexpr std::string_view
const template_file_mime_scalable =
"application-flatimage_{}.svg";
54constexpr std::string_view
const template_dir_apps_scalable =
"icons/hicolor/scalable/apps";
55constexpr std::string_view
const template_file_app_scalable =
"flatimage_{}.svg";
57constexpr const std::array<uint32_t, 9> arr_sizes {16,22,24,32,48,64,96,128,256};
59namespace fs = std::filesystem;
73 / std::vformat(template_dir_mimetype, std::make_format_args(size, size))
74 / std::vformat(template_file_mime, std::make_format_args(name_app));
76 / std::vformat(template_dir_apps, std::make_format_args(size, size))
77 / std::vformat(template_file_app, std::make_format_args(name_app));
78 return std::make_pair(path_file_mime,path_file_app);
91 / template_dir_mime_scalable
92 / std::vformat(template_file_mime_scalable, std::make_format_args(name_app));
94 / template_dir_apps_scalable
95 / std::vformat(template_file_app_scalable, std::make_format_args(name_app));
96 return std::make_pair(path_file_mime,path_file_app);
107 return xdg_data_home / std::format(
"applications/flatimage-{}.desktop", desktop.get_name());
118 return xdg_data_home / std::format(
"mime/packages/flatimage-{}.xml", desktop.get_name());
129 return xdg_data_home /
"mime/packages/flatimage.xml";
141 , fs::path
const& path_file_binary
145 os <<
"[Desktop Entry]" <<
'\n';
146 os << std::format(
"Name={}", desktop.get_name()) <<
'\n';
147 os <<
"Type=Application" <<
'\n';
148 os << std::format(
"Comment=FlatImage distribution of \"{}\"", desktop.get_name()) <<
'\n';
149 os << std::format(R
"(Exec="{}" %F)", path_file_binary.string()) << '\n';
150 os << std::format(
"Icon=flatimage_{}", desktop.get_name()) <<
'\n';
151 os << std::format(
"MimeType=application/flatimage_{};", desktop.get_name()) <<
'\n';
168 Try(fs::create_directories(path_file_desktop.parent_path()));
170 std::ofstream file_desktop(path_file_desktop, std::ios::out | std::ios::trunc);
171 return_if(not file_desktop.is_open() , Error(
"E::Could not open desktop file {}", path_file_desktop));
186 return_if(not fs::exists(path_entry_mimetype),
true,
"D::Update mime due to missing source file");
188 std::ifstream file_mimetype(path_entry_mimetype);
189 return_if(not file_mimetype.is_open(),
true,
"D::Update mime due to unaccessible source file for read");
191 std::string pattern = std::format(R
"(pattern="{}")", path_file_binary.filename().string());
192 for (std::string line; std::getline(file_mimetype, line);)
194 return_if(line.contains(pattern),
false,
"D::Mime pattern file name checks");
208 logger(
"I::Updating mime database '{}'", xdg_data_home);
210 .with_args(xdg_data_home /
"mime")
223 std::ofstream file_xml_generic(path_file_xml_generic, std::ios::out | std::ios::trunc);
224 return_if(not file_xml_generic.is_open(), Error(
"E::Could not open '{}'", path_file_xml_generic));
225 file_xml_generic << R
"(<?xml version="1.0" encoding="UTF-8"?>)" << '\n';
226 file_xml_generic << R
"(<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">)" << '\n';
227 file_xml_generic << R
"( <mime-type type="application/flatimage">)" << '\n';
228 file_xml_generic << R
"( <comment>FlatImage Application</comment>)" << '\n';
229 file_xml_generic << R
"( <magic>)" << '\n';
230 file_xml_generic << R
"( <match value="ELF" type="string" offset="1">)" << '\n';
231 file_xml_generic << R
"( <match value="0x46" type="byte" offset="8">)" << '\n';
232 file_xml_generic << R
"( <match value="0x49" type="byte" offset="9">)" << '\n';
233 file_xml_generic << R
"( <match value="0x01" type="byte" offset="10"/>)" << '\n';
234 file_xml_generic << R
"( </match>)" << '\n';
235 file_xml_generic << R
"( </match>)" << '\n';
236 file_xml_generic << R
"( </match>)" << '\n';
237 file_xml_generic << R
"( </magic>)" << '\n';
238 file_xml_generic << R
"( <glob weight="50" pattern="*.flatimage"/>)" << '\n';
239 file_xml_generic << R
"( <sub-class-of type="application/x-executable"/>)" << '\n';
240 file_xml_generic << R
"( <generic-icon name="application-flatimage"/>)" << '\n';
241 file_xml_generic << R
"( </mime-type>)" << '\n';
242 file_xml_generic << R
"(</mime-info>)" << '\n';
243 file_xml_generic.close();
255 , fs::path
const& path_file_binary
259 os << R
"(<?xml version="1.0" encoding="UTF-8"?>)" << '\n';
260 os << R
"(<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">)" << '\n';
261 os << std::format(R
"( <mime-type type="application/flatimage_{}">)", desktop.get_name()) << '\n';
262 os << R
"( <comment>FlatImage Application</comment>)" << '\n';
263 os << std::format(R
"( <glob weight="100" pattern="{}"/>)", path_file_binary.filename().string()) << '\n';
264 os << R
"( <sub-class-of type="application/x-executable"/>)" << '\n';
265 os << R
"( <generic-icon name="application-flatimage"/>)" << '\n';
266 os << R
"( </mime-type>)" << '\n';
267 os << R
"(</mime-info>)";
278 , fs::path
const& path_file_binary
284 fs::path path_dir_xml = path_file_xml.parent_path();
286 ,
"E::Could not create upper mimetype directories: {}", path_dir_xml
291 logger(
"I::Integrating mime database");
295 logger(
"D::Skipping mime database update");
299 std::ofstream file_xml(path_file_xml, std::ios::out | std::ios::trunc);
300 return_if(not file_xml.is_open(), Error(
"E::Could not open '{}'", path_file_xml));
320 auto f_copy_icon = [](fs::path
const& path_icon_src, fs::path
const& path_icon_dst)
322 logger(
"D::Copy '{}' to '{}'", path_icon_src, path_icon_dst);
324 if (not fs::exists(path_icon_dst, ec)
325 and not fs::copy_file(path_icon_src, path_icon_dst, ec))
327 logger(
"E::Could not copy file '{}' to '{}': '{}'"
330 , (ec)? ec.message() :
"Unknown error"
335 f_copy_icon(path_file_icon, path_icon_mimetype);
336 f_copy_icon(path_file_icon, path_icon_app);
349 auto f_create_parent = [](fs::path
const& path_src) ->
Value<void>
354 for(
auto&& size : arr_sizes)
358 Pop(f_create_parent(path_icon_mimetype));
359 Pop(f_create_parent(path_icon_app));
361 continue_if(fs::exists(path_icon_mimetype, ec));
365 if (not fs::copy_file(path_icon_mimetype, path_icon_app, fs::copy_options::skip_existing, ec))
367 logger(
"E::Could not copy file '{}': '{}'", path_icon_app, ec.message());
380 auto f_write_icon = [](fs::path
const& path_src) ->
Value<void>
383 std::ofstream file_src(path_src, std::ios::out | std::ios::trunc);
384 return_if(not file_src.is_open(), Error(
"E::Failed to open file '{}'", path_src));
385 file_src << ns_icon::FLATIMAGE;
391 fs::path path_icon_mime = path_dir_xdg / fs::path{template_dir_mime_scalable} /
"application-flatimage.svg";
392 Pop(f_write_icon(path_icon_mime));
394 fs::path path_icon_app = path_dir_xdg / fs::path{template_dir_apps_scalable} /
"flatimage.svg";
395 Pop(f_write_icon(path_icon_app));
410 fs::path path_file_icon = ({
413 fs::exists(path_file_icon_png, ec)? path_file_icon_png : path_file_icon_svg;
416 if (fs::exists(path_file_icon, ec))
418 logger(
"D::Icons are integrated, found {}", path_file_icon.string());
421 return_if(ec, Error(
"E::Could not check if icon exists: {}", ec.message()));
425 auto path_file_tmp_icon = fim.
path.dir.
app / std::format(
"icon.{}", icon.m_ext);
427 std::ofstream file_icon(path_file_tmp_icon, std::ios::out | std::ios::trunc);
428 return_if(not file_icon.is_open(), Error(
"E::Could not open temporary icon file for desktop integration"));
429 file_icon.write(icon.m_data, icon.m_size);
432 if ( path_file_tmp_icon.string().ends_with(
".svg") )
442 fs::remove(path_file_tmp_icon, ec);
443 return_if(ec, Error(
"E::Could not remove temporary icon file: {}", ec.message()));
459 ,
"E::Could not read desktop json from binary"
462 ,
"D::Missing or misconfigured desktop integration"
464 logger(
"D::Json desktop data: {}", str_raw_json);
467 if(desktop.get_integrations().contains(IntegrationItem::ENTRY))
469 logger(
"I::Integrating desktop entry");
470 Pop(integrate_desktop_entry(desktop, fim.
path.bin.
self));
473 if(desktop.get_integrations().contains(IntegrationItem::MIMETYPE))
475 logger(
"I::Integrating mime database");
476 Pop(integrate_mime_database(desktop, fim.
path.bin.
self));
479 if(desktop.get_integrations().contains(IntegrationItem::ICON))
481 logger(
"I::Integrating desktop icons");
482 if(
auto ret = integrate_icons(fim, desktop); not ret)
484 logger(
"D::Could not integrate icons: '{}'", ret.error());
493 fs::path path_file_icon = ({
494 fs::path path_file_icon_png = Pop(get_path_file_icon_png(desktop.get_name(), 64)).second;
495 fs::path path_file_icon_svg = Pop(get_path_file_icon_svg(desktop.get_name())).second;
496 Try(fs::exists(path_file_icon_png))? path_file_icon_png : path_file_icon_svg;
501 .
with_args(
"-c", R
"(notify-send "$@")", "--")
502 .
with_args(
"-i", path_file_icon, std::format(
"Started '{}' FlatImage", desktop.get_name()))
503 .
spawn()->wait().discard(
"E::Failed to send notification");
507 logger(
"D::Notify is disabled");
525 std::string icon_str{icon_path_or_url};
526 bool is_url = icon_str.starts_with(
"http://") or icon_str.starts_with(
"https://");
528 return_if (not is_url, fs::path{icon_str},
"D::Icon is a local file at '{}'", icon_str);
530 logger(
"I::Icon is an URL: {}", icon_str);
532 std::string extension = ({
533 auto pos = icon_str.find_last_of(
'.');
534 return_if(pos == std::string::npos, Error(
"E::Could not get icon file extension from url"));
535 icon_str.substr(pos);
537 logger(
"D::Resolved extension from URL: '{}'", extension);
541 fs::path path_file_icon_downloaded = fs::temp_directory_path() / (
"icon" + extension);
544 .
with_args(icon_str,
"-O", path_file_icon_downloaded)
547 return_if(not child, Error(
"E::Failed to spawn wget process"));
549 return_if (
int exit_code = child->wait().value_or(-1); exit_code != 0
550 , Error(
"E::wget failed with exit code: {} for URL: {}", exit_code, icon_str);
552 logger(
"I::Successfully downloaded icon to: {}", path_file_icon_downloaded);
553 return path_file_icon_downloaded;
567 std::ifstream file_json_src{path_file_json_src};
568 return_if(not file_json_src.is_open()
569 , Error(
"E::Failed to open file '{}' for desktop integration", path_file_json_src)
572 return_if(desktop.get_name().contains(
'/'), Error(
"E::Application name cannot contain the '/' character"));
574 auto icon_path_or_url = Pop(desktop.get_path_file_icon(),
"E::Could not retrieve icon path field from json");
575 fs::path path_file_icon = Pop(
setup_resolve_icon(icon_path_or_url.string()),
"E::Failed to resolve icon path or URL");
576 std::string str_ext = (path_file_icon.extension() ==
".svg")?
"svg"
577 : (path_file_icon.extension() ==
".png")?
"png"
578 : (path_file_icon.extension() ==
".jpg" or path_file_icon.extension() ==
".jpeg")?
"jpg"
580 return_if(str_ext.empty(), Error(
"E::Icon extension '{}' is not supported", path_file_icon.extension()));
583 std::streamsize size_file_icon = fs::file_size(path_file_icon, ec);
584 return_if(ec, Error(
"E::Could not get size of file '{}': {}", path_file_icon, ec.message()));
585 return_if(
static_cast<uint64_t
>(size_file_icon) >= ns_reserved::FIM_RESERVED_OFFSET_ICON_END - ns_reserved::FIM_RESERVED_OFFSET_ICON_BEGIN
586 , Error(
"E::File is too large, '{}' bytes", size_file_icon)
588 std::unique_ptr<char[]> ptr_data = std::make_unique<char[]>(size_file_icon);
589 std::streamsize bytes = Pop(
ns_reserved::read(path_file_icon, 0, ptr_data.get(), size_file_icon));
590 return_if(bytes != size_file_icon
591 , Error(
"E::Icon read bytes '{}' do not match target size of '{}'", bytes, size_file_icon)
593 std::make_pair(std::move(ptr_data), bytes);
597 std::memset(icon.m_ext, 0,
sizeof(icon.m_ext));
598 std::memcpy(icon.m_ext, str_ext.data(), std::min(str_ext.size(),
sizeof(icon.m_ext)-1));
599 std::memcpy(icon.m_data, image_data.first.get(), image_data.second);
600 icon.m_size = image_data.second;
605 auto db = Pop(
ns_db::from_string(str_raw_json),
"E::Could not parse serialized json source");
606 return_if(not db.erase(
"icon"), Error(
"E::Could not erase icon field"));
609 std::println(
"{}", Pop(db.dump()));
627 desktop.set_integrations(set_integrations);
629 for(
auto const& str_integration : set_integrations)
631 std::println(
"{}", std::string{str_integration});
653 auto integrations = desktop.get_integrations();
654 auto f_try_erase = [](fs::path
const& path)
657 fs::remove(path, ec);
660 logger(
"E::Could not remove '{}': {}", path, ec.message());
664 logger(
"I::Removed file '{}'", path);
668 if( integrations.contains(ns_db::ns_desktop::IntegrationItem::ENTRY))
670 f_try_erase(Pop(get_path_file_desktop(desktop)));
673 if(integrations.contains(ns_db::ns_desktop::IntegrationItem::MIMETYPE))
675 f_try_erase(Pop(get_path_file_mimetype(desktop)));
676 Pop(update_mime_database());
679 if(integrations.contains(ns_db::ns_desktop::IntegrationItem::ICON))
682 if(std::string_view(icon.m_ext) ==
"png")
684 for(
auto size : arr_sizes)
686 auto [path_icon_mime,path_icon_app] = Pop(get_path_file_icon_png(desktop.get_name(), size));
687 f_try_erase(path_icon_mime);
688 f_try_erase(path_icon_app);
693 auto [path_icon_mime,path_icon_app] = Pop(get_path_file_icon_svg(desktop.get_name()));
694 f_try_erase(path_icon_mime);
695 f_try_erase(path_icon_app);
713 return_if(std::all_of(icon.m_data, icon.m_data+
sizeof(icon.m_data), [](
char c){ return c == 0; })
714 , Error(
"E::Empty icon data");
717 std::string_view ext = icon.m_ext;
719 return_if(ext !=
"png" and ext !=
"svg", Error(
"E::Invalid file extension saved in desktop configuration"));
721 if(not path_file_dst.extension().string().ends_with(ext))
723 path_file_dst = path_file_dst.parent_path() / (path_file_dst.filename().
string() + std::format(
".{}", ext));
726 std::fstream file_dst(path_file_dst, std::ios::out | std::ios::trunc);
727 return_if(not file_dst.is_open(), Error(
"E::Could not open output file '{}'", path_file_dst));
729 file_dst.write(icon.m_data, icon.m_size);
731 return_if(not file_dst
732 , Error(
"E::Could not write all '{}' bytes to output file", icon.m_size)
750 std::stringstream ss;
751 Pop(generate_desktop_entry(desktop, fim.
path.bin.
self, ss));
769 std::stringstream ss;
770 Pop(generate_mime_database(desktop, fim.
path.bin.
self, ss));
std::unique_ptr< Child > spawn()
Spawns (forks) the child process and begins execution.
Subprocess & with_stdio(Stream mode)
Sets the stdio redirection mode for the child process.
Subprocess & with_args(Args &&... args)
Arguments forwarded as the process' arguments.
FlatImage configuration object.
Defines a class that manages FlatImage's desktop integration.
Enhanced error handling framework built on std::expected.
A library for operations on image files.
A library for manipulating environment variables.
#define logger(fmt,...)
Compile-time log level dispatch macro with automatic location capture.
Simplified macros for common control flow patterns with optional logging.
Value< std::string > serialize(Desktop const &desktop) noexcept
Serializes a Desktop class into a json string.
Value< Desktop > deserialize(std::string_view str_raw_json) noexcept
Deserializes a string into a Desktop class.
Value< Db > from_string(S &&s)
Parses a JSON string and creates a Db object.
Value< void > generate_mime_database(ns_db::ns_desktop::Desktop const &desktop, fs::path const &path_file_binary, std::ostream &os)
Generates the flatimage app specific mime package.
Value< std::pair< fs::path, fs::path > > get_path_file_icon_png(std::string_view name_app, uint32_t size)
Constructs the path to the png icon file.
Value< std::pair< fs::path, fs::path > > get_path_file_icon_svg(std::string_view name_app)
Constructs the path to the svg icon file.
Value< void > integrate_icons_svg(ns_db::ns_desktop::Desktop const &desktop, fs::path const &path_file_icon)
Integrates an svg icon for the flatimage mimetype.
Value< void > integrate_mime_database(ns_db::ns_desktop::Desktop const &desktop, fs::path const &path_file_binary)
Generates the flatimage app specific mime package.
Value< void > integrate_icons(ns_config::FlatImage const &fim, ns_db::ns_desktop::Desktop const &desktop)
Integrate flatimage icons in the current $HOME directory.
Value< void > integrate_desktop_entry(ns_db::ns_desktop::Desktop const &desktop, fs::path const &path_file_binary)
Integrates the desktop entry '.desktop'.
Value< fs::path > get_path_file_mimetype(ns_db::ns_desktop::Desktop const &desktop)
Get the file path to the application specific mimetype file.
Value< void > generate_desktop_entry(ns_db::ns_desktop::Desktop const &desktop, fs::path const &path_file_binary, std::ostream &os)
Generates the desktop entry.
Value< void > update_mime_database()
Runs update-mime-database on the current XDG_DATA_HOME diretory.
Value< fs::path > get_path_file_mimetype_generic()
Get the file path to the generic mimetype file.
bool is_update_mime_database(fs::path const &path_file_binary, fs::path const &path_entry_mimetype)
Checks if the mime database should be updated based on <glob pattern=
Value< void > integrate_icons_png(ns_db::ns_desktop::Desktop const &desktop, fs::path const &path_file_icon)
Integrates PNG icons for the flatimage mimetype.
Value< fs::path > get_path_file_desktop(ns_db::ns_desktop::Desktop const &desktop)
Get the file path to the desktop entry.
Value< void > integrate_mime_database_generic()
Generates the flatimage generic mime package.
Value< void > integrate_icon_flatimage()
Integrates the svg icon for the 'flatimage' mimetype.
Desktop integration command implementation.
Value< void > enable(ns_config::FlatImage const &fim, std::set< IntegrationItem > set_integrations)
Enables desktop integration for FlatImage.
Value< std::string > dump_entry(ns_config::FlatImage const &fim)
Dumps the desktop entry if integration is configured.
Value< void > clean(ns_config::FlatImage const &fim)
Cleans desktop integration files.
Value< void > setup(ns_config::FlatImage const &fim, fs::path const &path_file_json_src)
Setup desktop integration in FlatImage.
Value< std::string > dump_mimetype(ns_config::FlatImage const &fim)
Dumps the application mime type file if integration is configured.
Value< void > dump_icon(ns_config::FlatImage const &fim, fs::path path_file_dst)
Dumps the png or svg icon data to a file.
Value< fs::path > setup_resolve_icon(std::string_view icon_path_or_url)
Resolves icon path or URL to a local file path.
Value< void > integrate(ns_config::FlatImage const &fim)
Integrates flatimage desktop data in current system.
Value< fs::path > search_path(std::string const &query)
Search the directories in the PATH variable for the given input file name.
Value< T > xdg_data_home() noexcept
Returns or computes the value of XDG_DATA_HOME.
Value< fs::path > create_directories(fs::path const &p)
Creates directories recursively.
Value< void > resize(fs::path const &path_file_src, fs::path const &path_file_dst, uint32_t width, uint32_t height)
Resizes an input image to the specified width and height.
Value< void > write(fs::path const &path_file_binary, std::string_view const &json)
Writes the desktop json string to the target binary.
Value< std::string > read(fs::path const &path_file_binary)
Reads the desktop json string from the target binary.
Value< Icon > read(fs::path const &path_file_binary)
Reads the Icon struct from the target binary.
Value< void > write(fs::path const &path_file_binary, Icon const &icon)
Writes the Icon struct to the target binary.
Value< std::streamsize > read(fs::path const &path_file_binary, uint64_t offset, char *data, uint64_t length) noexcept
Reads data from a file in binary format.
std::string from_container(T &&t, std::optional< char > sep=std::nullopt) noexcept
Converts a container into a string if it has string convertible elements.
std::fstream & null()
Redirects to /dev/null (silent)
Stream
Stream redirection modes for child process stdio.
Contains the SVG data of the FlatImage icon.
Manages the desktop reserved space.
Manages the icon reserved space.
Enhanced expected type with integrated logging capabilities.
bool is_notify
Show desktop notifications? Stored in reserved space.
Main FlatImage configuration object.
Path path
Directory, file, and binary paths.
Flags flags
Runtime feature flags.
fs::path const self
Path to the FlatImage binary itself.
fs::path const app
Application directory (versioned by commit/timestamp)
Stores icon data in reserved space.
A library to spawn sub-processes in linux.