當有雙螢幕時,讓觸控螢幕在正確螢幕上觸控

最近多了一台觸控螢幕,接到主機後從原本的雙螢幕變成的三螢幕,其中一個就是這個觸控螢幕。
我的螢幕設定是使用延伸模式,主螢幕為原本雙螢幕的其中一個,加入第三個螢幕時一樣採取延伸模式。而在這個觸控螢幕在插上 USB 線後且安裝完驅動程式後,觸控螢幕就像觸控板一樣可以點擊來觸發滑鼠事件,但對應到的點擊區域卻是我的主螢幕,這完全沒道理啊!

本以為是驅動程式的問題,其實不是,而找到的解法有兩種:

  1. 將主螢幕設定到第三個螢幕,也就是觸控螢幕。
    這個方法不好!雖可以解決問題,但若像我一樣不希望第三個螢幕變成主螢幕的話就無法使用。

  2. 將觸控螢幕的對應到正確的螢幕上。
    其實就是把觸控螢幕當成一個螢幕加上觸控板,若用這樣的方式去想就知道該怎麼解了,設定方法如下。

打開控制台搜尋 Tablet PC 設定,或到開始功能表中直接搜尋 Tablet PC 設定。
控制台中的 Tablet PC 設定
開始功能表搜尋 Tablet PC 設定

直接點選設定中的設定。
Tablet PC 設定,點選設定

接著所有螢幕會變成白色,而其中一個將會顯示以下訊息,按 Enter 直到正確對應的螢幕上再觸控螢幕即可。
螢幕全白顯示,並跳出提示訊息

參考資料:

  1. Windows Touch Screen With Multiple Monitors Puts Cursor On Wrong Monitor - Super User

在 Logdown 上移除右上方 dashboard

最近朋友想開個部落格試試,我推薦了 Logdown、Blogger 或是全自己來的 GitHub Pages,最後友人選擇了 Logdown,然而友人跟我說:「Logdown 有個缺點,就是右上方的 Dashboard 很醜,要是能拿掉就太好了。」於是為了讓友人安心使用,我便看了一下 Logdown 原始碼,發現其實要拿掉或隱藏 Dashboard 並不困難。
被嫌醜的 Dashboard

到自己的 Dashboard 中點選 Blog 設定,接著點選佈景主題分頁,點選編輯 HTML,如下圖位置。
Blog 設定→佈景主題→編輯 HTML

在最下方 </body></html> 前面空白處插入以下程式碼:

1
2
3
4
5
6
<script>
document.addEventListener("DOMContentLoaded", function(event) {
var elem = document.querySelector('iframe[src="http://logdown.com/pages/top_controls"]');
elem.parentNode.removeChild(elem);
});
</script>

插入後呈現如下:
插入 javascript

接著重新開啟自己的 Logdown 看看就能看到成果囉!

※此段語法不適用於 IE8

取得 Parse server 推播通知送出狀態

Parse REST API Developers Guide 中,可以在 Quick Reference 看到所有使用的方法。在 Push Notification 分類中只有 POST 的方法,不像 Installations 可以透過 GET 方法到 /installations/<objectId> 來獲取已安裝裝置的資訊;然而若需要去擷取推播傳遞的狀態時,可以利用存於 mongoDB 中的 _PushStatus 來取得資訊,既然 push 的方法只有 POST,就繞個路把 _PushStatus 當成 Objects 來處理,Objects 的使用方法就多了。

URL HTTP Verb Functionality
/classes/<className> POST Creating Objects
/classes/<className>/<objectId> GET Retrieving Objects
/classes/<className>/<objectId> PUT Updating Objects
/classes/<className> GET Queries
/classes/<className>/<objectId> DELETE Deleting Objects

要取得推播通知狀態則是利用上表兩個 GET 的功能,若指定 objectId 則只回傳 objectId 那筆資訊,若不指定則回傳最近的幾筆資訊。

1
2
3
4
curl -X GET \
-H "X-Parse-Application-Id: parseAppId" \
-H "X-Parse-Master-Key: parseMasterKey" \
http://localhost:1337/parse/classes/_PushStatus/

正常下會得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"results":[
{"ACL":{}, "objectId": "xzBThEHVyc", "createdAt": "2016-05-19T10:00:59.827Z",…},
{"ACL":{}, "objectId": "VGo2rHGXjK", "createdAt": "2016-05-19T10:01:59.790Z",…},
{"ACL":{}, "objectId": "9rnRdakYzD", "createdAt": "2016-05-19T10:04:59.798Z",…},
{"ACL":{}, "objectId": "7pde1mbqzY", "createdAt": "2016-05-19T13:00:09.126Z",…},
{"ACL":{}, "objectId": "M2PmduKLH0", "createdAt": "2016-05-20T02:26:59.997Z",…},
{"ACL":{}, "objectId": "URiaNdDBps", "createdAt": "2016-05-20T02:35:59.991Z",…},
{"ACL":{}, "objectId": "eN8R4gVDEx", "createdAt": "2016-05-20T02:37:00.077Z",…},
{"ACL":{}, "objectId": "KRBif8iM6u", "createdAt": "2016-05-20T03:13:59.892Z",…},
{"ACL":{}, "objectId": "BP5FYt2GVX", "createdAt": "2016-05-20T03:24:59.846Z",…},
{"ACL":{}, "objectId": "1nSmDpZ3Yz", "createdAt": "2016-05-24T02:55:01.595Z",…},
{"ACL":{}, "objectId": "FDFjpei6rP", "createdAt": "2016-05-24T02:58:01.327Z",…},
{"ACL":{}, "objectId": "hTkD1EKO9U", "createdAt": "2016-05-24T03:02:01.608Z",…},
{"ACL":{}, "objectId": "TDT2bONPEF", "createdAt": "2016-05-24T07:51:02.636Z",…},
{"ACL":{}, "objectId": "tGuEYvGLbp", "createdAt": "2016-05-25T07:35:20.034Z",…}
]
}

而若加上 objectId,則會取得此 objectId 單筆資訊:

1
2
3
4
curl -X GET \
-H "X-Parse-Application-Id: parseAppId" \
-H "X-Parse-Master-Key: parseMasterKey" \
http://localhost:1337/parse/classes/_PushStatus/tGuEYvGLbp

正常下會得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"ACL": {},
"objectId": "tGuEYvGLbp",
"createdAt": "2016-05-25T07:35:20.034Z",
"pushTime": "2016-05-25T07:35:20.034Z",
"query": "{\"deviceType\":{\"$in\":[\"ios\"]},\"gender\":{\"$in\":[\"female\"]}}",
"payload": "{\"alert\":\"All work and no play makes Jack a dull boy.\"}",
"source": "rest",
"status": "succeeded",
"numSent": 1,
"pushHash": "cd3f8c70e6cfecc66ab69a0ee4c3c564",
"updatedAt": "2016-05-25T07:35:22.600Z",
"numFailed": 0,
"sentPerType": {
"ios": 1
}
}

兩者都可取得 numSent, numFailedsentPerType,可利於推播通知的統計分析。

參考資料:

  1. REST API Developers Guide | Parse

隱藏 console 中的 source 來源

先前有發現 Facebook 在開發人員工具的 console 中最右邊不會顯示 log 的來源行數,覺得神奇,但並沒有深入了解如何實作此效果。而最近看到有人提問同樣的問題,同樣以 Facebook 的 console 為例來發問,激起了我的求知慾望。
Facebook 的 console,最右端沒顯示來源
正常呼叫 console.log 時,最右端會顯示來源

單純要查到此功能似乎不難,剛好在四個月前有人在 stackoverflow 問了同樣的問題,而方法就是利用下方寫法即可:

1
setTimeout(console.log.bind(console, "Hello world!"));

看到 Facebook 將 console 的預設樣式改掉,因此我也試著去看該怎麼做,果然樣式是用 CSS 去設定,而要設定的部分前面記得放個 %c,若有多段需要不同樣式則放入多個 %c,每個 %c 都是獨立的,樣式不會互相覆蓋;而 CSS 以額外參數的方式傳遞,若有 N 個 %c,則需要另外傳入 N 個參數。範例如下:

1
2
3
4
5
console.log("%cRed color", "color:red;"); // 顯示為紅色字
console.log("%cRed color with blue background", "color:red; background:blue;"); // 顯示紅色字與藍色背景

console.log("%cRed color and %cgreen color", "color:red;", "color:green;"); // 顯示紅色字即綠色字
console.log("%cRed color%c and %cgreen color", "color:red;", "", "color:green;"); // 顯示紅色字即綠色字,將 "and" 改回預設樣式

兩者結合起來的話,一樣將樣式參數放於之後:

1
setTimeout(console.log.bind(console, "%cRed color", "color:red;"));

因此我寫了一個範例,可以開啟開發人員工具的 console 觀看結果:

See the Pen Custom console log messages by North (@ssk7833) on CodePen.

好,問題解決了!
但痛苦來臨,我走不出自己這關 XD,深問 why?為什麼 setTimeout(console.log.bind(console, "something")) 能有如此魔力使來源行數隱藏起來?式了很多方式去包裝 console 還是徒勞無功,且 setTimeout 好像是必要的?自行寫了一個具有 callback function 的 function 也無法達到一樣效果。

後來想到或許能在 stack trace 中得到一點線索,因此改下 console.error 試試看,結果還是深深地打了我一巴掌,stack trace 竟然完全是空的。
比較 error 上下的差別

有人跟我說,會不會是 call 到 browser 的 console 再從 console 下 command 才會這樣顯示出來?結果也不是,也是跳出了來源。
使用開發者工具下指令結果一樣

真是奇怪啊!百思不得其解後,決定先「不求甚解」了,等待哪一天突然想到再補充吧。

2016-05-26 Update:

最後跑去 stackoverflow 問了,也有人回答了讓我似懂非懂的答案,因有些不確定性的部分並沒提到,總之可能就先這樣,哪天想到再繼續研究。

參考資料:

  1. How to hide source of Log messages in Console?
  2. Colors in JavaScript console
  3. Console API Reference | Web Tools - Google Developers
  4. Format Specifiers

Parse 推播回傳 _pushStatus 的 id

在使用 parse server REST API 的推播功能時,推播成功送到 parse server 只會時回傳 {"result":true},只有這資訊對於後台串接 parse server 不是很方便,因缺乏 _pushStatus 中的 id,導致資料送出後就一去無回,不方便針對各個送出到 parse server 的資料做對應。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
curl -X POST \
-H "X-Parse-Application-Id: parseAppId" \
-H "X-Parse-Master-Key: parseMasterKey" \
-H "Content-Type: application/json" \
-d '{
"where": {
"deviceType": {
"$in": [
"ios"
]
}
},
"data": {
"title": "The Shining",
"alert": "All work and no play makes Jack a dull boy."
}
}' \

http://localhost:1337/parse/push/

正常下會得到:

1
{"result":true}

所幸在 parse server 2.2.7 版時新增了 X-Parse-Push-Status-Id 這個 header,如同現行的 Parse.com 一樣,因為考量到回傳 object id 可能會暴露給 clients,因此放在 response 的 header 中。如何驗證 header 確實存在 X-Parse-Push-Status-Id,可以利用 curl 加上 -v 來看整個 request, response 的結果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
curl -X POST \
-H "X-Parse-Application-Id: parseAppId" \
-H "X-Parse-Master-Key: parseMasterKey" \
-H "Content-Type: application/json" \
-d '{
"where": {
"deviceType": {
"$in": [
"ios"
]
}
},
"data": {
"title": "The Shining",
"alert": "All work and no play makes Jack a dull boy."
}
}' \

http://localhost:1337/parse/push/ \
-v

得到類似結果,回傳的 header 中就能看到 X-Parse-Push-Status-Id:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 1337 (#0)
> POST /parse/push/ HTTP/1.1
> User-Agent: curl/7.41.0
> Host: localhost:1337
> Accept: */*
> X-Parse-Application-Id: parseAppId
> X-Parse-Master-Key: parseMasterKey
> Content-Type: application/json
> Content-Length: 125
>
* upload completely sent off: 125 out of 125 bytes
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS
< Access-Control-Allow-Headers: X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, Content-Type
< X-Parse-Push-Status-Id: gLdaMNg12i
< Content-Type: application/json; charset=utf-8
< Content-Length: 15
< Date: Fri, 20 May 2016 07:51:58 GMT
< Connection: keep-alive
<
{"result":true}* Connection #0 to host localhost left intact

有了這個資訊後,就可以在後台中做到許多變化,以下為 node.js 的範例:

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
var http = require('http');

var config = {
parseLocation: 'localhost',
parsePort: 1337,
parsePush: '/parse/push',
parseAppId: 'parseAppId',
parseMasterKey:'parseMasterKey',
};

// 範例用 object
var obj = {
"where": {
"deviceType": {
"$in": ["ios"]
}
},
"data": {
"title": "The Shining",
"alert": "All work and no play makes Jack a dull boy."
}
}

var req = http.request({
host: config.parseLocation,
port: config.parsePort,
path: config.parsePush,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': config.parseAppId,
'X-Parse-Master-Key': config.parseMasterKey
}
}, function(res) {
var jsonString = '';
res.on('data', (chunk) => {
jsonString += chunk;
});

res.on('end', () => {
var json = JSON.parse(jsonString);
if(json.result===true) {
// 在這裡確認 result 為 true 後,將 x-parse-push-status-id 存在某個 obj 中來做對應
console.log(res.headers['x-parse-push-status-id']); // 印出 x-parse-push-status-id
obj.pushId = res.headers['x-parse-push-status-id'];
}
})
});

var postData = JSON.stringify(obj);

req.write(postData);
req.end();

最後 obj 會得到類似結果:

1
2
3
4
5
6
7
8
9
10
11
12
{
"where": {
"deviceType": {
"$in": ["ios"]
}
},
"data": {
"title": "The Shining",
"alert": "All work and no play makes Jack a dull boy."
},
"pushId": "kAuJhPqpt9"
}

接下來怎麼儲存物件及使用這些資料就是另一個課題了~

參考資料:

  1. Adds X-Parse-Push-Status-Id header
  2. Return PushStatus ID from push endpoint.
  3. Sending Pushes