env_preferences/
linux.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

use std::{collections::HashMap, ffi::CStr, ptr};

use libc::{setlocale, LC_ALL, LC_TIME};
use std::str::FromStr;

use crate::RetrievalError;

#[derive(Hash, Eq, PartialEq, Debug)]
pub enum LocaleCategory {
    Character,
    Number,
    Time,
    Collate,
    Monetary,
    Messages,
    Paper,
    Name,
    Address,
    Telephone,
    Measurement,
    Identification,
    All,
}

impl FromStr for LocaleCategory {
    type Err = RetrievalError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "LC_CTYPE" => Ok(Self::Character),
            "LC_NUMERIC" => Ok(Self::Number),
            "LC_TIME" => Ok(Self::Time),
            "LC_COLLATE" => Ok(Self::Collate),
            "LC_MONETARY" => Ok(Self::Monetary),
            "LC_MESSAGES" => Ok(Self::Messages),
            "LC_PAPER" => Ok(Self::Paper),
            "LC_NAME" => Ok(Self::Name),
            "LC_ADDRESS" => Ok(Self::Address),
            "LC_TELEPHONE" => Ok(Self::Telephone),
            "LC_MEASUREMENT" => Ok(Self::Measurement),
            "LC_IDENTIFICATION" => Ok(Self::Identification),
            "LC_ALL" => Ok(Self::All),
            _ => Err(RetrievalError::UnknownCategory),
        }
    }
}

// TODO: Add a function to return all the locales POSIX categories explicitly

/// Retrieves locale for `LC_ALL` POSIX category. Also returns other categories if any are explicitly
/// set in the thread
pub fn get_locales() -> Result<HashMap<LocaleCategory, String>, RetrievalError> {
    let mut locale_map = HashMap::new();

    // SAFETY: Safety is ensured because we pass a `NULL` pointer and retrieve the locale there is
    // no subsequent calls for `setlocale` which could change the locale of this particular thread
    let locales_ptr = unsafe { setlocale(LC_ALL, ptr::null()) };

    if locales_ptr.is_null() {
        return Err(RetrievalError::NullLocale);
    }

    // SAFETY: A valid `NULL` terminator is present which is a requirement of `from_ptr`
    let locales_str = unsafe { CStr::from_ptr(locales_ptr) }.to_str()?;
    let locale_pairs = locales_str.split(';');
    for locale_pair in locale_pairs {
        let mut parts = locale_pair.split('=');
        if let Some(value) = parts.next() {
            if let Some(key) = parts.next() {
                if let Ok(category) = LocaleCategory::from_str(value) {
                    locale_map.insert(category, key.to_string());
                }
            } else {
                // Handle case where only a single locale
                locale_map.insert(LocaleCategory::All, value.to_string());
            }
        }
    }
    Ok(locale_map)
}

/// Get the system calendar locale (LC_TIME).
///
/// This only returns the calendar locale, `gnome-calendar` is the default calendar in linux
/// The locale returned is for `Gregorian` calendar
/// Related issue: `<https://gitlab.gnome.org/GNOME/gnome-calendar/-/issues/998>`
pub fn get_system_calendars() -> Result<String, RetrievalError> {
    // SAFETY: Safety is ensured because we pass a `NULL` pointer and retrieve the locale there is
    // no subsequent calls for `setlocale` which could change the locale of this particular thread
    let locale_ptr = unsafe { setlocale(LC_TIME, ptr::null()) };

    if !locale_ptr.is_null() {
        // SAFETY: A valid `NULL` terminator is present which is a requirement of `from_ptr`
        let rust_str = unsafe { CStr::from_ptr(locale_ptr) }.to_str()?;
        let calendar_locale = rust_str.to_string();
        return Ok(calendar_locale);
    }
    Err(RetrievalError::NullLocale)
}