mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-05 16:35:33 +00:00
203 lines
No EOL
7.8 KiB
TypeScript
203 lines
No EOL
7.8 KiB
TypeScript
// Includes version-related functionality
|
|
///<reference path="utils/styleConstants.ts" />
|
|
|
|
// The Device object definition -------------------------------------------------
|
|
|
|
namespace com.keyman {
|
|
export class Device {
|
|
touchable: boolean;
|
|
OS: string;
|
|
formFactor: string;
|
|
dyPortrait: number;
|
|
dyLandscape: number;
|
|
version: string;
|
|
orientation: string|number;
|
|
browser: string;
|
|
colorScheme: 'light' | 'dark';
|
|
|
|
private detected: boolean = false;
|
|
private _styles: utils.StyleConstants;
|
|
|
|
// Generates a default Device value.
|
|
constructor() {
|
|
this.touchable = !!('ontouchstart' in window);
|
|
this.OS = '';
|
|
this.formFactor='desktop';
|
|
this.dyPortrait=0;
|
|
this.dyLandscape=0;
|
|
this.version='0';
|
|
this.orientation=window.orientation;
|
|
this.browser='';
|
|
}
|
|
|
|
/**
|
|
* Get device horizontal DPI for touch devices, to set actual size of active regions
|
|
* Note that the actual physical DPI may be somewhat different.
|
|
*
|
|
* @return {number}
|
|
*/
|
|
getDPI(): number {
|
|
var t=document.createElement('DIV') ,s=t.style,dpi=96;
|
|
if(document.readyState !== 'complete') {
|
|
return dpi;
|
|
}
|
|
|
|
t.id='calculateDPI';
|
|
s.position='absolute'; s.display='block';s.visibility='hidden';
|
|
s.left='10px'; s.top='10px'; s.width='1in'; s.height='10px';
|
|
document.body.appendChild(t);
|
|
dpi=(typeof window.devicePixelRatio == 'undefined') ? t.offsetWidth : t.offsetWidth * window.devicePixelRatio;
|
|
document.body.removeChild(t);
|
|
return dpi;
|
|
}
|
|
|
|
detect() : void {
|
|
var possMacSpoof = false;
|
|
|
|
if(navigator && navigator.userAgent) {
|
|
var agent=navigator.userAgent;
|
|
|
|
if(agent.indexOf('iPad') >= 0) {
|
|
this.OS='iOS';
|
|
this.formFactor='tablet';
|
|
this.dyPortrait=this.dyLandscape=0;
|
|
} else if(agent.indexOf('iPhone') >= 0) {
|
|
this.OS='iOS';
|
|
this.formFactor='phone';
|
|
this.dyPortrait=this.dyLandscape=25;
|
|
} else if(agent.indexOf('Android') >= 0) {
|
|
this.OS='Android';
|
|
this.formFactor='phone'; // form factor may be redefined on initialization
|
|
this.dyPortrait=75;
|
|
this.dyLandscape=25;
|
|
try {
|
|
var rx=new RegExp("(?:Android\\s+)(\\d+\\.\\d+\\.\\d+)");
|
|
this.version=agent.match(rx)[1];
|
|
} catch(ex) {}
|
|
} else if(agent.indexOf('Linux') >= 0) {
|
|
this.OS='Linux';
|
|
} else if(agent.indexOf('Macintosh') >= 0) {
|
|
// Starting with 13.1, "Macintosh" can reflect iPads (by default) or iPhones
|
|
// (by user setting); a new "Request Desktop Website" setting for Safari will
|
|
// change the user agent string to match a desktop Mac.
|
|
//
|
|
// Firefox uses '.' between version components, while Chrome and Safari use
|
|
// '_' instead. So, we have to check for both. Yay.
|
|
let regex = /Intel Mac OS X (\d+(?:[_\.]\d+)+)/i;
|
|
let results = regex.exec(agent);
|
|
|
|
// Match result: a version string with components separated by underscores.
|
|
if(!results) {
|
|
console.warn("KMW could not properly parse the user agent string."
|
|
+ "A suboptimal keyboard layout may result.");
|
|
this.OS='MacOSX';
|
|
} else if(results.length > 1 && results[1]) {
|
|
// Convert version string into a usable form.
|
|
let versionString = results[1].replace('_', '.');
|
|
let version = new utils.Version(versionString);
|
|
|
|
possMacSpoof = utils.Version.MAC_POSSIBLE_IPAD_ALIAS.compareTo(version) <= 0;
|
|
this.OS='MacOSX';
|
|
}
|
|
} else if(agent.indexOf('Windows NT') >= 0) {
|
|
this.OS='Windows';
|
|
if(agent.indexOf('Touch') >= 0) {
|
|
this.formFactor='phone'; // will be redefined as tablet if resolution high enough
|
|
}
|
|
|
|
// Windows Phone and Tablet PC
|
|
if(typeof (<any>navigator).msMaxTouchPoints == 'number' && (<any>navigator).msMaxTouchPoints > 0) {
|
|
this.touchable=true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// We look at the screen resolution for Android, because we can't tell from
|
|
// the user agent string whether or not this is supposed to be a tablet.
|
|
// It seems that there are a handful of older phones out there that report a
|
|
// higher resolution than 700px*___px, but it is proving hard to test these,
|
|
// and the majority have an aspect ratio <= 0.5625 anyway.
|
|
// But we trust what iOS tells us for phone vs tablet.
|
|
|
|
const dimMin = Math.min(screen.width,screen.height), dimMax = Math.max(screen.width,screen.height);
|
|
const aspect = dimMin / dimMax;
|
|
|
|
if(this.OS != 'iOS' &&
|
|
this.formFactor == 'phone' &&
|
|
((dimMin >= 600 && aspect > 0.5625) || // 0.5625 -> 1920x1080 is common phone res
|
|
(aspect >= 0.625)) // all reported devices with aspect >= 0.625 are tablets per https://screensiz.es/
|
|
) {
|
|
this.formFactor='tablet';
|
|
}
|
|
|
|
// Test for potential Chrome emulation on Windows or macOS X (used only in next if-check)
|
|
let possibleChromeEmulation = navigator.platform == 'Win32' || navigator.platform == 'MacIntel'
|
|
|
|
// alert(sxx+'->'+device.formFactor);
|
|
// Check for phony iOS devices (but don't undo for Chrome emulation used during development)
|
|
if(this.OS == 'iOS' && !('ongesturestart' in window) && !possibleChromeEmulation) {
|
|
this.OS='Android';
|
|
}
|
|
|
|
// Determine application or browser
|
|
this.browser='web';
|
|
if(this.OS == 'iOS' || this.OS.toLowerCase() == 'macosx') {
|
|
this.browser='safari';
|
|
}
|
|
|
|
var bMatch=/Firefox|Chrome|OPR|Safari|Edge/;
|
|
if(bMatch.test(navigator.userAgent)) {
|
|
if((navigator.userAgent.indexOf('Firefox') >= 0) && ('onmozorientationchange' in screen)) {
|
|
this.browser='firefox';
|
|
} else if(navigator.userAgent.indexOf('OPR') >= 0) {
|
|
this.browser='opera';
|
|
} else if(navigator.userAgent.indexOf(' Edge/') >= 0) {
|
|
// Edge is too common a word, so test for Edge/ :)
|
|
// Must come before Chrome and Safari test because
|
|
// Edge pretends to be both
|
|
this.browser='edge';
|
|
} else if(navigator.userAgent.indexOf('Chrome') >= 0) {
|
|
// This test must come before Safari test because on macOS,
|
|
// Chrome also reports "Safari"
|
|
this.browser='chrome';
|
|
} else if(navigator.userAgent.indexOf('Safari') >= 0) {
|
|
this.browser='safari';
|
|
}
|
|
}
|
|
|
|
if(possMacSpoof && this.browser == 'safari') {
|
|
// Indistinguishable user agent string! We need a different test; fortunately, true macOS
|
|
// Safari doesn't support TouchEvents. (Chrome does, though! Hence the filter above.)
|
|
if(window['TouchEvent']) {
|
|
this.OS='iOS';
|
|
this.formFactor='tablet';
|
|
this.dyPortrait=this.dyLandscape=0;
|
|
|
|
// It's currently impossible to differentiate between iPhone and iPad here
|
|
// except for by screen dimensions.
|
|
let aspectRatio = screen.height / screen.width;
|
|
if(aspectRatio < 1) {
|
|
aspectRatio = 1 / aspectRatio;
|
|
}
|
|
|
|
// iPhones usually have a ratio of 16:9 (or 1.778) or higher, while iPads use 4:3 (or 1.333)
|
|
if(aspectRatio > 1.6) {
|
|
// Override - we'll treat this device as an iPhone.
|
|
this.formFactor = 'phone';
|
|
this.dyPortrait=this.dyLandscape=25;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.colorScheme = utils.StyleConstants.prefersDarkMode() ? 'dark' : 'light';
|
|
this.detected = true;
|
|
}
|
|
|
|
/**
|
|
* Returns a slimmer, web-core compatible version of this object.
|
|
*/
|
|
public get coreSpec(): utils.DeviceSpec {
|
|
return new utils.DeviceSpec(this.browser, this.formFactor, this.OS, this.touchable);
|
|
}
|
|
}
|
|
} |