Skip to content

Commit

Permalink
Supporting cases where tags are written only as text labels without s…
Browse files Browse the repository at this point in the history
…pecifying the color index explicitly
  • Loading branch information
mikekazakov committed Jan 20, 2024
1 parent dafc04c commit 467ff8d
Show file tree
Hide file tree
Showing 2 changed files with 383 additions and 7 deletions.
347 changes: 340 additions & 7 deletions Source/Utility/source/Tags.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,324 @@ static constexpr const char *g_FinderInfo = "com.apple.FinderInfo";
[[clang::no_destroy]] static const std::string g_LabelRed = "Red";
[[clang::no_destroy]] static const std::string g_LabelOrange = "Orange";

// Run this script to rebuild the table:
// for i in {0..7}; do key="TG_COLOR_$i"; find /System/Library/CoreServices/Finder.app/Contents/Resources -name
// "Localizable.strings" -exec /usr/libexec/PlistBuddy -c "Print :$key" {} \; | sed -n "s/\(.*\)/{\"&\", $i},/p"; done
[[clang::no_destroy]] static const robin_hood::unordered_flat_map<std::string, uint8_t> g_LocalizedToColors{
{"Ohne Farbe", 0},
{"ללא צבע", 0},
{"No Colour", 0},
{"لا يوجد لون", 0},
{"Κανένα χρώμα", 0},
{"カラーなし", 0},
{"No Color", 0},
{"Немає кольору", 0},
{"Sin color", 0},
{"无颜色", 0},
{"Sin color", 0},
{"Nenhuma Cor", 0},
{"Ingen farve", 0},
{"Nessun colore", 0},
{"Žiadna farba", 0},
{"Nenhuma cor", 0},
{"Tiada Warna", 0},
{"Ingen färg", 0},
{"Žádná barva", 0},
{"색상 없음", 0},
{"Ingen farge", 0},
{"Nincs szín", 0},
{"沒有顏色", 0},
{"Renk Yok", 0},
{"Brak koloru", 0},
{"沒有顏色", 0},
{"No Colour", 0},
{"Không có màu", 0},
{"Без цвета", 0},
{"Aucune couleur", 0},
{"Aucune couleur", 0},
{"Ei väriä", 0},
{"Tidak Ada Warna", 0},
{"Geen kleur", 0},
{"ไม่มีสี", 0},
{"Nicio culoare", 0},
{"Bez boje", 0},
{"कोई रंग नहीं", 0},
{"Cap color", 0},
{"Grau", 1},
{"אפור", 1},
{"Grey", 1},
{"رمادي", 1},
{"Γκρι", 1},
{"グレイ", 1},
{"Gray", 1},
{"Сірий", 1},
{"Gris", 1},
{"灰色", 1},
{"Gris", 1},
{"Cinza", 1},
{"Grå", 1},
{"Grigio", 1},
{"Sivá", 1},
{"Cinzento", 1},
{"Kelabu", 1},
{"Grå", 1},
{"Šedá", 1},
{"회색", 1},
{"Grå", 1},
{"Szürke", 1},
{"灰色", 1},
{"Gri", 1},
{"Szary", 1},
{"灰色", 1},
{"Grey", 1},
{"Xám", 1},
{"Серый", 1},
{"Gris", 1},
{"Gris", 1},
{"Harmaa", 1},
{"Abu-Abu", 1},
{"Grijs", 1},
{"สีเทา", 1},
{"Gri", 1},
{"Siva", 1},
{"धूसर", 1},
{"Grisa", 1},
{"Grün", 2},
{"ירוק", 2},
{"Green", 2},
{"أخضر", 2},
{"Πράσινο", 2},
{"グリーン", 2},
{"Green", 2},
{"Зелений", 2},
{"Verde", 2},
{"绿色", 2},
{"Verde", 2},
{"Verde", 2},
{"Grøn", 2},
{"Verde", 2},
{"Zelená", 2},
{"Verde", 2},
{"Hijau", 2},
{"Grön", 2},
{"Zelená", 2},
{"초록색", 2},
{"Grønn", 2},
{"Zöld", 2},
{"綠色", 2},
{"Yeşil", 2},
{"Zielony", 2},
{"綠色", 2},
{"Green", 2},
{"Lục", 2},
{"Зеленый", 2},
{"Vert", 2},
{"Vert", 2},
{"Vihreä", 2},
{"Hijau", 2},
{"Groen", 2},
{"สีเขียว", 2},
{"Verde", 2},
{"Zelena", 2},
{"हरा", 2},
{"Verda", 2},
{"Lila", 3},
{"סגול", 3},
{"Purple", 3},
{"أرجواني", 3},
{"Μοβ", 3},
{"パープル", 3},
{"Purple", 3},
{"Бузковий", 3},
{"Morado", 3},
{"紫色", 3},
{"Morado", 3},
{"Roxo", 3},
{"Lilla", 3},
{"Viola", 3},
{"Fialová", 3},
{"Roxo", 3},
{"Ungu", 3},
{"Lila", 3},
{"Fialová", 3},
{"보라색", 3},
{"Lilla", 3},
{"Bíbor", 3},
{"紫色", 3},
{"Mor", 3},
{"Purpurowy", 3},
{"紫色", 3},
{"Purple", 3},
{"Tía", 3},
{"Лиловый", 3},
{"Violet", 3},
{"Violet", 3},
{"Violetti", 3},
{"Ungu", 3},
{"Paars", 3},
{"สีม่วง", 3},
{"Mov", 3},
{"Ljubičasta", 3},
{"जामुनी", 3},
{"Morada", 3},
{"Blau", 4},
{"כחול", 4},
{"Blue", 4},
{"أزرق", 4},
{"Μπλε", 4},
{"ブルー", 4},
{"Blue", 4},
{"Синій", 4},
{"Azul", 4},
{"蓝色", 4},
{"Azul", 4},
{"Azul", 4},
{"Blå", 4},
{"Blu", 4},
{"Modrá", 4},
{"Azul", 4},
{"Biru", 4},
{"Blå", 4},
{"Modrá", 4},
{"파란색", 4},
{"Blå", 4},
{"Kék", 4},
{"藍色", 4},
{"Mavi", 4},
{"Niebieski", 4},
{"藍色", 4},
{"Blue", 4},
{"Lam", 4},
{"Синий", 4},
{"Bleu", 4},
{"Bleu", 4},
{"Sininen", 4},
{"Biru", 4},
{"Blauw", 4},
{"สีน้ำเงิน", 4},
{"Albastru", 4},
{"Plava", 4},
{"नीला", 4},
{"Blava", 4},
{"Gelb", 5},
{"צהוב", 5},
{"Yellow", 5},
{"أصفر", 5},
{"Κίτρινο", 5},
{"イエロー", 5},
{"Yellow", 5},
{"Жовтий", 5},
{"Amarillo", 5},
{"黄色", 5},
{"Amarillo", 5},
{"Amarelo", 5},
{"Gul", 5},
{"Giallo", 5},
{"Žltá", 5},
{"Amarelo", 5},
{"Kuning", 5},
{"Gul", 5},
{"Žlutá", 5},
{"노란색", 5},
{"Gul", 5},
{"Sárga", 5},
{"黃色", 5},
{"Sarı", 5},
{"Żółty", 5},
{"黃色", 5},
{"Yellow", 5},
{"Vàng", 5},
{"Желтый", 5},
{"Jaune", 5},
{"Jaune", 5},
{"Keltainen", 5},
{"Kuning", 5},
{"Geel", 5},
{"สีเหลือง", 5},
{"Galben", 5},
{"Žuta", 5},
{"पीला", 5},
{"Groga", 5},
{"Rot", 6},
{"אדום", 6},
{"Red", 6},
{"أحمر", 6},
{"Κόκκινο", 6},
{"レッド", 6},
{"Red", 6},
{"Червоний", 6},
{"Rojo", 6},
{"红色", 6},
{"Rojo", 6},
{"Vermelho", 6},
{"Rød", 6},
{"Rosso", 6},
{"Červená", 6},
{"Vermelho", 6},
{"Merah", 6},
{"Röd", 6},
{"Červená", 6},
{"빨간색", 6},
{"Rød", 6},
{"Piros", 6},
{"紅色", 6},
{"Kırmızı", 6},
{"Czerwony", 6},
{"紅色", 6},
{"Red", 6},
{"Đỏ", 6},
{"Красный", 6},
{"Rouge", 6},
{"Rouge", 6},
{"Punainen", 6},
{"Merah", 6},
{"Rood", 6},
{"สีแดง", 6},
{"Roșu", 6},
{"Crvena", 6},
{"लाल", 6},
{"Vermella", 6},
{"Orange", 7},
{"כתום", 7},
{"Orange", 7},
{"برتقالي", 7},
{"Πορτοκαλί", 7},
{"オレンジ", 7},
{"Orange", 7},
{"Оранжевий", 7},
{"Naranja", 7},
{"橙色", 7},
{"Naranja", 7},
{"Laranja", 7},
{"Orange", 7},
{"Arancione", 7},
{"Oranžová", 7},
{"Laranja", 7},
{"Jingga", 7},
{"Orange", 7},
{"Oranžová", 7},
{"주황색", 7},
{"Oransje", 7},
{"Narancs", 7},
{"橙色", 7},
{"Turuncu", 7},
{"Pomarańczowy", 7},
{"橙色", 7},
{"Orange", 7},
{"Cam", 7},
{"Оранжевый", 7},
{"Orange", 7},
{"Orange", 7},
{"Oranssi", 7},
{"Oranye", 7},
{"Oranje", 7},
{"สีส้ม", 7},
{"Portocaliu", 7},
{"Narančasta", 7},
{"नारंगी", 7},
{"Taronja", 7},
};

namespace {

struct Trailer {
Expand Down Expand Up @@ -108,7 +426,7 @@ static std::optional<Tags::Tag> ParseTag(std::u16string_view _tag_rep) noexcept
if( _tag_rep.empty() )
return {};

Tags::Color color = Tags::Color::None;
std::optional<Tags::Color> color;
if( _tag_rep.size() >= 3 && //
std::byteswap(_tag_rep[_tag_rep.size() - 2]) == '\x0a' && //
std::byteswap(_tag_rep[_tag_rep.size() - 1]) >= '0' && //
Expand All @@ -125,19 +443,34 @@ static std::optional<Tags::Tag> ParseTag(std::u16string_view _tag_rep) noexcept
kCFStringEncodingUTF16BE,
false,
kCFAllocatorNull));
if( cf_str ) {
if( const char *cstr = CFStringGetCStringPtr(cf_str.get(), kCFStringEncodingUTF8) )
return Tags::Tag{InternalizeString(cstr), color};
if( !cf_str )
return {};

const std::string *label = nullptr;
if( const char *cstr = CFStringGetCStringPtr(cf_str.get(), kCFStringEncodingUTF8) ) {
label = InternalizeString(cstr);
}
else {
const CFIndex length = CFStringGetLength(cf_str.get());
const CFIndex max_size = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
std::array<char, 4096> mem_buffer;
std::pmr::monotonic_buffer_resource mem_resource(mem_buffer.data(), mem_buffer.size());
std::pmr::vector<char> str_buf(max_size, &mem_resource);
if( CFStringGetCString(cf_str.get(), str_buf.data(), max_size, kCFStringEncodingUTF8) )
return Tags::Tag{InternalizeString(str_buf.data()), color};
label = InternalizeString(str_buf.data());
}
return {};

if( label == nullptr )
return {};

if( color == std::nullopt ) {
// Old versions of MacOS can write only a label without the tag color index, relying on the value of the label
// to deduce the color. That's for "base" colors. In these cases - try to do the same.
auto it = g_LocalizedToColors.find(*label);
color = it == g_LocalizedToColors.end() ? Tags::Color::None : Tags::Color{it->second};
}

return Tags::Tag{label, *color};
}

namespace {
Expand Down Expand Up @@ -272,7 +605,7 @@ std::vector<Tags::Tag> Tags::ReadTags(int _fd) noexcept
const ssize_t res = flistxattr(_fd, buf.data(), buf.size(), 0);
if( res <= 0 )
return {};

// 1st - try MDItemUserTags
const bool has_usertags =
memmem(buf.data(), res, g_MDItemUserTags, std::string_view{g_MDItemUserTags}.length()) != nullptr;
Expand Down
Loading

0 comments on commit 467ff8d

Please sign in to comment.