mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 04:55:42 +09:00
fix(desktop): keep the KDE tray icon full size (#3496)
This commit is contained in:
1 parent
c424656bad
commit
8f4c966d3a
3 files changed
+12
-70
No files matched your search
@@ -15,7 +15,6 @@ import {getDesktopUpdateState, observeDesktopUpdateState} from '@electron/main/D
|
|||||||
import {checkForUpdatesFromShell, startDesktopUpdateFromShell} from '@electron/main/DesktopUpdatePrompt';
|
import {checkForUpdatesFromShell, startDesktopUpdateFromShell} from '@electron/main/DesktopUpdatePrompt';
|
||||||
import {shouldDisableModuleUpdatesForLaunch} from '@electron/main/LaunchOptions';
|
import {shouldDisableModuleUpdatesForLaunch} from '@electron/main/LaunchOptions';
|
||||||
import {relaunchStableLaunchPath} from '@electron/main/LinuxLaunchPath';
|
import {relaunchStableLaunchPath} from '@electron/main/LinuxLaunchPath';
|
||||||
import {getLinuxDesktop} from '@electron/main/LinuxSession';
|
|
||||||
import {onLocaleChange, t} from '@electron/main/MainI18n';
|
import {onLocaleChange, t} from '@electron/main/MainI18n';
|
||||||
import {renderTrayIcon, resolveTrayIconAppearance} from '@electron/main/TrayIconImage';
|
import {renderTrayIcon, resolveTrayIconAppearance} from '@electron/main/TrayIconImage';
|
||||||
import {describeUnreadBadge} from '@electron/main/UnreadBadge';
|
import {describeUnreadBadge} from '@electron/main/UnreadBadge';
|
||||||
@@ -259,8 +258,7 @@ function resolveTrayIcon(): {key: string; image: Electron.NativeImage} | null {
|
|||||||
const key = `${appearance}:${indicator}`;
|
const key = `${appearance}:${indicator}`;
|
||||||
let image = trayIconCache.get(key);
|
let image = trayIconCache.get(key);
|
||||||
if (!image) {
|
if (!image) {
|
||||||
const padded = process.platform === 'linux' && getLinuxDesktop() === 'kde';
|
image = renderTrayIcon(trayBaseIcon, appearance, indicator);
|
||||||
image = renderTrayIcon(trayBaseIcon, appearance, indicator, padded);
|
|
||||||
trayIconCache.set(key, image);
|
trayIconCache.set(key, image);
|
||||||
}
|
}
|
||||||
return {key, image};
|
return {key, image};
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import {BLUE_BGRA, discBitmap, FakeNativeImage, fakeNativeImageModule, pixel} fr
|
|||||||
|
|
||||||
installElectronStub({nativeImage: fakeNativeImageModule});
|
installElectronStub({nativeImage: fakeNativeImageModule});
|
||||||
|
|
||||||
const {padTrayIconBitmap, paintMonochromeGlyph, paintTrayIndicatorDot, renderTrayIcon, resolveTrayIconAppearance} =
|
const {paintMonochromeGlyph, paintTrayIndicatorDot, renderTrayIcon, resolveTrayIconAppearance} = await import(
|
||||||
await import('./TrayIconImage.ts');
|
'./TrayIconImage.ts'
|
||||||
|
);
|
||||||
|
|
||||||
describe('tray icon appearance', () => {
|
describe('tray icon appearance', () => {
|
||||||
test('macOS always uses the template image', () => {
|
test('macOS always uses the template image', () => {
|
||||||
@@ -68,32 +69,20 @@ describe('unread dot', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('padTrayIconBitmap', () => {
|
|
||||||
test('centers the bitmap in a transparent 22:16 canvas', () => {
|
|
||||||
const padded = padTrayIconBitmap(Buffer.alloc(16 * 16 * 4, 0xff), 16, 16);
|
|
||||||
assert.equal(padded.width, 22);
|
|
||||||
assert.equal(padded.height, 22);
|
|
||||||
assert.deepEqual(pixel(padded.buffer, 22, 2, 2), [0, 0, 0, 0]);
|
|
||||||
assert.deepEqual(pixel(padded.buffer, 22, 3, 3), [0xff, 0xff, 0xff, 0xff]);
|
|
||||||
assert.deepEqual(pixel(padded.buffer, 22, 18, 18), [0xff, 0xff, 0xff, 0xff]);
|
|
||||||
assert.deepEqual(pixel(padded.buffer, 22, 19, 19), [0, 0, 0, 0]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('renderTrayIcon', () => {
|
describe('renderTrayIcon', () => {
|
||||||
test('returns the base image untouched for the colored icon without a dot', () => {
|
test('returns the base image untouched for the colored icon without a dot', () => {
|
||||||
const base = FakeNativeImage.fromBitmap(16);
|
const base = FakeNativeImage.fromBitmap(16);
|
||||||
assert.equal(renderTrayIcon(base, 'colored', false, false), base);
|
assert.equal(renderTrayIcon(base, 'colored', false), base);
|
||||||
const template = FakeNativeImage.fromBitmap(16);
|
const template = FakeNativeImage.fromBitmap(16);
|
||||||
template.setTemplateImage(true);
|
template.setTemplateImage(true);
|
||||||
assert.equal(renderTrayIcon(template, 'template', false, false), template);
|
assert.equal(renderTrayIcon(template, 'template', false), template);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('paints every scale factor and keeps the template flag', () => {
|
test('paints every scale factor and keeps the template flag', () => {
|
||||||
const base = FakeNativeImage.fromBitmap(16);
|
const base = FakeNativeImage.fromBitmap(16);
|
||||||
base.addRepresentation({scaleFactor: 2, width: 32, height: 32, buffer: discBitmap(32)});
|
base.addRepresentation({scaleFactor: 2, width: 32, height: 32, buffer: discBitmap(32)});
|
||||||
base.setTemplateImage(true);
|
base.setTemplateImage(true);
|
||||||
const result = renderTrayIcon(base, 'template', true, false);
|
const result = renderTrayIcon(base, 'template', true);
|
||||||
assert.notEqual(result, base);
|
assert.notEqual(result, base);
|
||||||
assert.deepEqual(result.getScaleFactors(), [1, 2]);
|
assert.deepEqual(result.getScaleFactors(), [1, 2]);
|
||||||
assert.equal(result.isTemplateImage(), true);
|
assert.equal(result.isTemplateImage(), true);
|
||||||
@@ -102,36 +91,16 @@ describe('renderTrayIcon', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('combines the monochrome glyph with a red dot', () => {
|
test('combines the monochrome glyph with a red dot', () => {
|
||||||
const result = renderTrayIcon(FakeNativeImage.fromBitmap(32), 'white', true, false);
|
const result = renderTrayIcon(FakeNativeImage.fromBitmap(32), 'white', true);
|
||||||
assert.equal(result.isTemplateImage(), false);
|
assert.equal(result.isTemplateImage(), false);
|
||||||
const bitmap = result.toBitmap();
|
const bitmap = result.toBitmap();
|
||||||
assert.deepEqual(pixel(bitmap, 32, 25, 7), [0x00, 0x00, 0xdd, 0xff]);
|
assert.deepEqual(pixel(bitmap, 32, 25, 7), [0x00, 0x00, 0xdd, 0xff]);
|
||||||
assert.deepEqual(pixel(bitmap, 32, 6, 16), [0xff, 0xff, 0xff, 0xff]);
|
assert.deepEqual(pixel(bitmap, 32, 6, 16), [0xff, 0xff, 0xff, 0xff]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('pads the glyph to the 16px-in-22px tray grid at every scale', () => {
|
|
||||||
const base = FakeNativeImage.fromBitmap(16);
|
|
||||||
base.addRepresentation({scaleFactor: 2, width: 32, height: 32, buffer: discBitmap(32)});
|
|
||||||
const result = renderTrayIcon(base, 'colored', false, true);
|
|
||||||
assert.notEqual(result, base);
|
|
||||||
assert.deepEqual(result.getSize(1), {width: 22, height: 22});
|
|
||||||
assert.deepEqual(result.getSize(2), {width: 22, height: 22});
|
|
||||||
const bitmap = result.toBitmap();
|
|
||||||
assert.deepEqual(pixel(bitmap, 22, 11, 1), [0, 0, 0, 0]);
|
|
||||||
assert.deepEqual(pixel(bitmap, 22, 11, 4), BLUE_BGRA);
|
|
||||||
assert.deepEqual(pixel(result.toBitmap({scaleFactor: 2}), 44, 22, 7), BLUE_BGRA);
|
|
||||||
assert.deepEqual(pixel(result.toBitmap({scaleFactor: 2}), 44, 22, 5), [0, 0, 0, 0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('keeps the dot on the glyph corner when padded', () => {
|
|
||||||
const bitmap = renderTrayIcon(FakeNativeImage.fromBitmap(32), 'white', true, true).toBitmap();
|
|
||||||
assert.deepEqual(pixel(bitmap, 44, 31, 13), [0x00, 0x00, 0xdd, 0xff]);
|
|
||||||
assert.deepEqual(pixel(bitmap, 44, 42, 1), [0, 0, 0, 0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('skips representations whose bitmap does not match their size', () => {
|
test('skips representations whose bitmap does not match their size', () => {
|
||||||
const base = FakeNativeImage.fromBitmap(16);
|
const base = FakeNativeImage.fromBitmap(16);
|
||||||
base.toBitmap = () => Buffer.alloc(3);
|
base.toBitmap = () => Buffer.alloc(3);
|
||||||
assert.equal(renderTrayIcon(base, 'white', true, false), base);
|
assert.equal(renderTrayIcon(base, 'white', true), base);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -12,7 +12,6 @@ const SUBSAMPLES = 4;
|
|||||||
const MIN_GLYPH_CONTRAST = 32;
|
const MIN_GLYPH_CONTRAST = 32;
|
||||||
const GLYPH_CUTOUT_START = 0.1;
|
const GLYPH_CUTOUT_START = 0.1;
|
||||||
const GLYPH_CUTOUT_END = 0.5;
|
const GLYPH_CUTOUT_END = 0.5;
|
||||||
const KDE_TRAY_CANVAS_RATIO = 22 / 16;
|
|
||||||
const DOT_BGR: Bgr = [0x00, 0x00, 0xdd];
|
const DOT_BGR: Bgr = [0x00, 0x00, 0xdd];
|
||||||
const TEMPLATE_DOT_BGR: Bgr = [0x00, 0x00, 0x00];
|
const TEMPLATE_DOT_BGR: Bgr = [0x00, 0x00, 0x00];
|
||||||
const GLYPH_BGR: Readonly<Record<'white' | 'black', Bgr>> = {
|
const GLYPH_BGR: Readonly<Record<'white' | 'black', Bgr>> = {
|
||||||
@@ -115,30 +114,9 @@ export function paintTrayIndicatorDot(bitmap: Buffer, width: number, height: num
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function padTrayIconBitmap(
|
export function renderTrayIcon(base: NativeImage, appearance: TrayIconAppearance, indicator: boolean): NativeImage {
|
||||||
bitmap: Buffer,
|
|
||||||
width: number,
|
|
||||||
height: number,
|
|
||||||
): {buffer: Buffer; width: number; height: number} {
|
|
||||||
const paddedWidth = Math.round(width * KDE_TRAY_CANVAS_RATIO);
|
|
||||||
const paddedHeight = Math.round(height * KDE_TRAY_CANVAS_RATIO);
|
|
||||||
const left = Math.floor((paddedWidth - width) / 2);
|
|
||||||
const top = Math.floor((paddedHeight - height) / 2);
|
|
||||||
const buffer = Buffer.alloc(paddedWidth * paddedHeight * 4);
|
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
bitmap.copy(buffer, ((top + y) * paddedWidth + left) * 4, y * width * 4, (y + 1) * width * 4);
|
|
||||||
}
|
|
||||||
return {buffer, width: paddedWidth, height: paddedHeight};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function renderTrayIcon(
|
|
||||||
base: NativeImage,
|
|
||||||
appearance: TrayIconAppearance,
|
|
||||||
indicator: boolean,
|
|
||||||
padded: boolean,
|
|
||||||
): NativeImage {
|
|
||||||
const monochrome = appearance === 'white' || appearance === 'black';
|
const monochrome = appearance === 'white' || appearance === 'black';
|
||||||
if (!monochrome && !indicator && !padded) {
|
if (!monochrome && !indicator) {
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
const result = nativeImage.createEmpty();
|
const result = nativeImage.createEmpty();
|
||||||
@@ -155,10 +133,7 @@ export function renderTrayIcon(
|
|||||||
if (indicator) {
|
if (indicator) {
|
||||||
paintTrayIndicatorDot(bitmap, width, height, appearance === 'template' ? TEMPLATE_DOT_BGR : DOT_BGR);
|
paintTrayIndicatorDot(bitmap, width, height, appearance === 'template' ? TEMPLATE_DOT_BGR : DOT_BGR);
|
||||||
}
|
}
|
||||||
result.addRepresentation({
|
result.addRepresentation({scaleFactor, width, height, buffer: bitmap});
|
||||||
scaleFactor,
|
|
||||||
...(padded ? padTrayIconBitmap(bitmap, width, height) : {width, height, buffer: bitmap}),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (result.isEmpty()) {
|
if (result.isEmpty()) {
|
||||||
return base;
|
return base;
|
||||||
|
|||||||
Reference in new issue
Block a user