Skip to content

Commit 0378532

Browse files
committed
feat(wallpapers-bing): restore preferred-region picker
Adds a Bing Preferred Region SelectionRow to get_settings_widget() -- 14 options (All Markets, No Preference, plus 13 individual markets labelled Region - Market), reading/writing ~/.config/ncz-wallpaper/bing-markets, the same file cix-installer's 45-wallpaper-rotator.sh ncz-wallpaper-bing helper already reads. Ports a complete, working implementation (fa5a38a on singularity-shell, zoder-reviewed, full ninja+meson test pass) that lived inline on the Desktop settings page before the plugin-based wallpaper manager (132efb7) deleted it as collateral damage of that rewrite -- it never had a new home in the plugin architecture. Every market is always fetched and combined by the helper regardless of this setting; the picker only chooses whose caption/credit wins when the same photo is shared across regions. Verified: full ninja -C builddir build succeeds (0 errors) against a fresh subproject checkout of libsingularity. Assisted-by: Claude Code:claude-sonnet-5 AI scope: ported the deleted desktop_page.vala implementation into the plugin's settings widget, adjusted class/field placement for the plugin context.
1 parent d255d47 commit 0378532

1 file changed

Lines changed: 198 additions & 0 deletions

File tree

‎wallpapers-bing/wallpapers_bing.vala‎

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ using GLib;
22
using Gtk;
33
using Gee;
44
using Singularity;
5+
using Singularity.Widgets;
56
using Peas;
67

78
[ModuleInit]
@@ -61,9 +62,41 @@ namespace WallpapersBing {
6162
}
6263
}
6364

65+
// Bing market row: 2-letter market code, UI display label, and region
66+
// bucket ("Americas" / "Europe" / "Asia-Pacific") used as the label
67+
// prefix in the settings SelectionRow's option list. Plain GLib.Object
68+
// rather than a struct so it can be stored in a Gee.ArrayList (Vala
69+
// disallows array types as generic type arguments).
70+
private class BingMarketEntry : GLib.Object {
71+
public string code { get; set; }
72+
public string label { get; set; }
73+
public string region { get; set; }
74+
}
75+
6476
public class WallpapersBingPlugin : Object, Singularity.Plugin {
6577
private PluginContext context;
6678
private WallpapersBing.Provider? provider;
79+
private const string BING_MARKETS_ID_ALL = "all";
80+
// 13 markets, grouped by region. Order matches the comment block in
81+
// cix-installer/post-install/45-wallpaper-rotator.sh's
82+
// ncz-wallpaper-bing (Americas, Europe, Asia-Pacific).
83+
// [0] = market code, [1] = display label, [2] = region header.
84+
private const string BING_MARKETS_TABLE = "en-US\tUnited States\tAmericas"
85+
+ "|en-CA\tCanada English\tAmericas"
86+
+ "|fr-CA\tCanada French\tAmericas"
87+
+ "|pt-BR\tBrazil\tAmericas"
88+
+ "|en-GB\tUnited Kingdom\tEurope"
89+
+ "|fr-FR\tFrance\tEurope"
90+
+ "|de-DE\tGermany\tEurope"
91+
+ "|es-ES\tSpain\tEurope"
92+
+ "|it-IT\tItaly\tEurope"
93+
+ "|en-IN\tIndia\tAsia-Pacific"
94+
+ "|ja-JP\tJapan\tAsia-Pacific"
95+
+ "|zh-CN\tChina\tAsia-Pacific"
96+
+ "|ko-KR\tSouth Korea\tAsia-Pacific";
97+
private Gee.ArrayList<BingMarketEntry> bing_markets_rows = new Gee.ArrayList<BingMarketEntry>();
98+
private SelectionRow? bing_markets_row = null;
99+
private bool bing_markets_updating = false;
67100

68101
public void activate(PluginContext ctx) {
69102
this.context = ctx;
@@ -84,6 +117,171 @@ public class WallpapersBingPlugin : Object, Singularity.Plugin {
84117
lbl.wrap = true;
85118
lbl.xalign = 0;
86119
box.append(lbl);
120+
121+
// Preferred-region selector. Every market is always fetched and
122+
// combined by the ncz-wallpaper-bing helper; this only sets which
123+
// region's copy of a duplicate photo wins the caption/credit when
124+
// the same photo is shared across regions -- see
125+
// ~/.config/ncz-wallpaper/bing-markets, which
126+
// 45-wallpaper-rotator.sh's ncz-wallpaper-bing reads verbatim.
127+
// A single inline SelectionRow (not a popup dialog): "All
128+
// Markets, No Preference" first, then all 13 markets from
129+
// BING_MARKETS_TABLE labelled "<Region> - <Market>" so the region
130+
// grouping survives as label text without section headers.
131+
init_bing_markets_table();
132+
var bing_markets_options = new Gee.ArrayList<Singularity.Core.AppSettingOption>();
133+
bing_markets_options.add(new Singularity.Core.AppSettingOption() { id = BING_MARKETS_ID_ALL, label = _("All Markets, No Preference") });
134+
foreach (var market in bing_markets_rows) {
135+
bing_markets_options.add(new Singularity.Core.AppSettingOption() {
136+
id = market.code, label = "%s - %s".printf(market.region, market.label) });
137+
}
138+
// Initial selection reflects the file: "all" or absent = All
139+
// Markets; otherwise the first configured market code (a
140+
// preference is singular). Falls back to "all" if the file names
141+
// a code that isn't in the current table, so the row always
142+
// opens on a real entry.
143+
string bing_markets_current = BING_MARKETS_ID_ALL;
144+
if (!bing_markets_file_is_all()) {
145+
string[] configured = bing_markets_read_codes();
146+
if (configured.length > 0) {
147+
foreach (var opt in bing_markets_options) {
148+
if (opt.id == configured[0]) { bing_markets_current = configured[0]; break; }
149+
}
150+
}
151+
}
152+
bing_markets_row = new SelectionRow.with_options(_("Bing Preferred Region"), bing_markets_options,
153+
bing_markets_current);
154+
bing_markets_row.subtitle = _("Bing always combines every region's photo of the day; this only picks whose caption and credit win when the same photo is shared");
155+
bing_markets_row.selected.connect((id) => {
156+
if (bing_markets_updating || bing_markets_row == null) return;
157+
if (id == BING_MARKETS_ID_ALL) {
158+
write_bing_markets_all();
159+
} else {
160+
write_bing_markets_codes({id});
161+
}
162+
});
163+
box.append(bing_markets_row);
87164
return box;
88165
}
166+
167+
// Lower-case an ASCII string. Vala's GLib string has no public
168+
// lowercase() (only casefold(), which is Unicode-aware and therefore
169+
// locale-sensitive -- the bing-market codes are all ISO 639-1 +
170+
// ISO 3166-1 letters, so a literal ASCII fold is both correct and
171+
// cheaper).
172+
private static string ascii_lower(string s) {
173+
string out = "";
174+
for (int i = 0; i < s.length; i++) {
175+
char c = s[i];
176+
if (c >= 'A' && c <= 'Z') c = (char)(c + 32);
177+
out += c.to_string();
178+
}
179+
return out;
180+
}
181+
182+
// Parse BING_MARKETS_TABLE into bing_markets_rows ({code, label,
183+
// region}), in table order. Called once per get_settings_widget()
184+
// build, before the SelectionRow option list is built from it.
185+
private void init_bing_markets_table() {
186+
bing_markets_rows.clear();
187+
foreach (string entry in BING_MARKETS_TABLE.split("|")) {
188+
string[] cols = entry.split("\t");
189+
if (cols.length != 3) continue;
190+
var row = new BingMarketEntry() { code = cols[0], label = cols[1], region = cols[2] };
191+
bing_markets_rows.add(row);
192+
}
193+
}
194+
195+
// Full path to the bing-markets file the cix-installer rotator
196+
// already reads. Lives under XDG_CONFIG_HOME so it tracks the user
197+
// even when $HOME is relocated for test sessions.
198+
private string bing_markets_file_path() {
199+
return GLib.Path.build_filename(
200+
GLib.Environment.get_user_config_dir(),
201+
"ncz-wallpaper",
202+
"bing-markets");
203+
}
204+
205+
// Read the bing-markets file and report whether its content
206+
// (trimmed, lowercased) is the "all" sentinel -- i.e. "no preferred
207+
// region". Absent file also returns true so the SelectionRow starts
208+
// on "All Markets, No Preference" on a fresh install, matching the
209+
// rotator's own default (preferred_market() in
210+
// 45-wallpaper-rotator.sh returns None for an absent file too).
211+
private bool bing_markets_file_is_all() {
212+
string path = bing_markets_file_path();
213+
if (!FileUtils.test(path, FileTest.EXISTS)) return true;
214+
string text;
215+
try {
216+
FileUtils.get_contents(path, out text);
217+
} catch (Error e) {
218+
return true;
219+
}
220+
return ascii_lower(text.strip()) == "all";
221+
}
222+
223+
// Read the bing-markets file and return the configured codes as an
224+
// array. "all" (any case) or absent -> empty list. Otherwise split
225+
// on any of whitespace/comma and keep tokens matching the
226+
// 2-letter-2-letter market pattern, preserving file order. Only the
227+
// FIRST entry is ever honoured as the preference, but every matched
228+
// token is returned so a legacy multi-market file degrades to "the
229+
// first one wins" rather than silently losing the whole value.
230+
private string[] bing_markets_read_codes() {
231+
string path = bing_markets_file_path();
232+
if (!FileUtils.test(path, FileTest.EXISTS)) return {};
233+
string text;
234+
try {
235+
FileUtils.get_contents(path, out text);
236+
} catch (Error e) {
237+
return {};
238+
}
239+
if (ascii_lower(text.strip()) == "all") return {};
240+
string[] codes = {};
241+
string[] seen = {};
242+
foreach (string tok in text.strip().split_set(" \t\n,")) {
243+
if (tok.length == 0) continue;
244+
if (tok.length != 5 || tok[2] != '-') continue;
245+
bool dup = false;
246+
foreach (string existing in seen) if (existing == tok) { dup = true; break; }
247+
if (dup) continue;
248+
seen += tok;
249+
codes += tok;
250+
}
251+
return codes;
252+
}
253+
254+
// Atomic write of a single-line contents string to the bing-markets
255+
// file, so the daemon (which polls the file) never reads a
256+
// half-flushed value. Creates the directory if absent. Silent on
257+
// failure -- the daemon's default kicks in if the file is missing.
258+
private void write_bing_markets_contents(string contents) {
259+
string path = bing_markets_file_path();
260+
string dir = GLib.Path.get_dirname(path);
261+
try {
262+
GLib.DirUtils.create_with_parents(dir, 0700);
263+
string tmp = path + ".tmp";
264+
FileUtils.set_contents(tmp, contents);
265+
if (FileUtils.rename(tmp, path) != 0) {
266+
warning("bing markets: could not rename %s into place", path);
267+
}
268+
} catch (Error e) {
269+
warning("bing markets: could not write %s: %s", path, e.message);
270+
}
271+
}
272+
273+
private void write_bing_markets_all() {
274+
write_bing_markets_contents("all\n");
275+
}
276+
277+
// Write the chosen preferred market as a single line. Empty list ->
278+
// fall back to "all" rather than an empty file, making the user's
279+
// "no preferred region" intent explicit on disk.
280+
private void write_bing_markets_codes(string[] codes) {
281+
if (codes.length == 0) {
282+
write_bing_markets_all();
283+
return;
284+
}
285+
write_bing_markets_contents(string.joinv(" ", codes) + "\n");
286+
}
89287
}

0 commit comments

Comments
 (0)