@@ -19,6 +19,10 @@ global.HARDWARE = global.HARDWARE || {
19
19
} ,
20
20
notifiers : [ ] ,
21
21
} ,
22
+ keyboard : {
23
+ visible : null ,
24
+ notifiers : [ ] ,
25
+ } ,
22
26
} ;
23
27
24
28
/**
@@ -69,6 +73,23 @@ const init = async (args) => {
69
73
console . log ( `\nDisplay Status [${ HARDWARE . display . status . path } ]:` , getDisplayStatus ( ) ) ;
70
74
console . log ( `Display Brightness [${ HARDWARE . display . brightness . path } ]:` , getDisplayBrightness ( ) , "\n" ) ;
71
75
76
+ // Check for keyboard visibility changes
77
+ HARDWARE . support . keyboardVisibility = processRuns ( "squeekboard" ) ;
78
+ setKeyboardVisibility ( "OFF" , ( reply , error ) => {
79
+ if ( ! reply || error ) {
80
+ return ;
81
+ }
82
+ dbusMonitor ( "/sm/puri/OSK0" , ( property , error ) => {
83
+ if ( ! property || error ) {
84
+ return ;
85
+ }
86
+ HARDWARE . keyboard . visibility = property . Visible === "true" ;
87
+ HARDWARE . keyboard . notifiers . forEach ( ( notifier ) => {
88
+ notifier ( ) ;
89
+ } ) ;
90
+ } ) ;
91
+ } ) ;
92
+
72
93
// Check for display changes
73
94
setInterval ( update , 500 ) ;
74
95
@@ -124,9 +145,12 @@ const compatibleDevice = () => {
124
145
/**
125
146
* Queries the session type for the logged in user.
126
147
*
127
- * @returns {string } Returns session type x11/ wayland or null if an error occurs.
148
+ * @returns {string } Returns session type ' x11'/' wayland' or null if an error occurs.
128
149
*/
129
150
const sessionType = ( ) => {
151
+ if ( ! commandExists ( "loginctl" ) ) {
152
+ return null ;
153
+ }
130
154
return execSyncCommand ( "loginctl" , [
131
155
"show-session" ,
132
156
"$(loginctl show-user $(whoami) -p Display --value)" ,
@@ -146,7 +170,7 @@ const session = (type) => {
146
170
/**
147
171
* Gets the model name using `/sys/firmware/devicetree/base/model` or `/sys/class/dmi/id/product_name`.
148
172
*
149
- * @returns {string|null } The model name of the device or " Generic" if not found.
173
+ * @returns {string|null } The model name of the device or ' Generic' if not found.
150
174
*/
151
175
const getModel = ( ) => {
152
176
const paths = [ "/sys/firmware/devicetree/base/model" , "/sys/class/dmi/id/product_name" ] ;
@@ -161,7 +185,7 @@ const getModel = () => {
161
185
/**
162
186
* Gets the vendor name using `/sys/class/dmi/id/board_vendor`.
163
187
*
164
- * @returns {string|null } The vendor name of the device or " Generic" if not found.
188
+ * @returns {string|null } The vendor name of the device or ' Generic' if not found.
165
189
*/
166
190
const getVendor = ( ) => {
167
191
const paths = [ "/sys/class/dmi/id/board_vendor" ] ;
@@ -180,7 +204,7 @@ const getVendor = () => {
180
204
/**
181
205
* Gets the serial number using `/sys/firmware/devicetree/base/serial-number` or `/sys/class/dmi/id/product_serial`.
182
206
*
183
- * @returns {string|null } The serial number of the device or " 123456" if not found.
207
+ * @returns {string|null } The serial number of the device or ' 123456' if not found.
184
208
*/
185
209
const getSerialNumber = ( ) => {
186
210
const paths = [ "/sys/firmware/devicetree/base/serial-number" , "/sys/class/dmi/id/product_serial" ] ;
@@ -195,7 +219,7 @@ const getSerialNumber = () => {
195
219
/**
196
220
* Gets the host machine id using `/etc/machine-id`.
197
221
*
198
- * @returns {string } The machine id of the system or " 123456" if not found.
222
+ * @returns {string } The machine id of the system or ' 123456' if not found.
199
223
*/
200
224
const getMachineId = ( ) => {
201
225
return execSyncCommand ( "cat" , [ "/etc/machine-id" ] ) || "123456" ;
@@ -290,7 +314,7 @@ const getDisplayStatusPath = () => {
290
314
/**
291
315
* Gets the current display power status using `wlopm` or `xset`.
292
316
*
293
- * @returns {string|null } The display status ON/ OFF or null if an error occurs.
317
+ * @returns {string|null } The display status as 'ON'/' OFF' or null if an error occurs.
294
318
*/
295
319
const getDisplayStatus = ( ) => {
296
320
if ( ! HARDWARE . support . displayStatus ) {
@@ -323,6 +347,7 @@ const getDisplayStatus = () => {
323
347
*/
324
348
const setDisplayStatus = ( status , callback = null ) => {
325
349
if ( ! HARDWARE . support . displayStatus ) {
350
+ if ( typeof callback === "function" ) callback ( null , "Not supported" ) ;
326
351
return ;
327
352
}
328
353
if ( status !== "ON" && status !== "OFF" ) {
@@ -398,6 +423,7 @@ const getDisplayBrightness = () => {
398
423
*/
399
424
const setDisplayBrightness = ( brightness , callback = null ) => {
400
425
if ( ! HARDWARE . support . displayBrightness ) {
426
+ if ( typeof callback === "function" ) callback ( null , "Not supported" ) ;
401
427
return ;
402
428
}
403
429
if ( typeof brightness !== "number" || brightness < 1 || brightness > 100 ) {
@@ -412,6 +438,52 @@ const setDisplayBrightness = (brightness, callback = null) => {
412
438
proc . stdin . end ( ) ;
413
439
} ;
414
440
441
+ /**
442
+ * Gets the keyboard visibility using global properties.
443
+ *
444
+ * @returns {string|null } The keyboard visibility as 'ON'/'OFF' or null if an error occurs.
445
+ */
446
+ const getKeyboardVisibility = ( ) => {
447
+ if ( ! HARDWARE . support . keyboardVisibility ) {
448
+ return null ;
449
+ }
450
+ return HARDWARE . keyboard . visibility ? "ON" : "OFF" ;
451
+ } ;
452
+
453
+ /**
454
+ * Sets the keyboard visibility using `dbus-send`.
455
+ *
456
+ * This function takes a desired visibility ('ON' or 'OFF') and executes
457
+ * the appropriate command to show or hide the keyboard.
458
+ *
459
+ * @param {boolean } visibility - The desired visibility ('ON' or 'OFF').
460
+ * @param {function } callback - A callback function that receives the output or error.
461
+ */
462
+ const setKeyboardVisibility = ( visibility , callback = null ) => {
463
+ if ( ! HARDWARE . support . keyboardVisibility ) {
464
+ if ( typeof callback === "function" ) callback ( null , "Not supported" ) ;
465
+ return ;
466
+ }
467
+ const visible = visibility === "ON" ;
468
+ HARDWARE . keyboard . visibility = visible ;
469
+ dbusCall ( "/sm/puri/OSK0" , "SetVisible" , [ `boolean:${ visible } ` ] , callback ) ;
470
+ } ;
471
+
472
+ /**
473
+ * Checks if system upgrades are available using `apt`.
474
+ *
475
+ * @returns {Array<string> } A list of package names that are available for upgrade.
476
+ */
477
+ const checkPackageUpgrades = ( ) => {
478
+ if ( ! commandExists ( "apt" ) ) {
479
+ return [ ] ;
480
+ }
481
+ const output = execSyncCommand ( "apt" , [ "list" , "--upgradable" , "2>/dev/null" ] ) ;
482
+ const packages = ( output || "" ) . trim ( ) . split ( "\n" ) ;
483
+ packages . shift ( ) ;
484
+ return packages ;
485
+ } ;
486
+
415
487
/**
416
488
* Shuts down the system using `sudo shutdown -h now`.
417
489
*
@@ -437,14 +509,27 @@ const rebootSystem = (callback = null) => {
437
509
} ;
438
510
439
511
/**
440
- * Checks if a command exists using `which `.
512
+ * Checks if a process is running using `pidof `.
441
513
*
442
- * @param {string } cmd - The command name to check.
443
- * @returns {boolean } Returns true if the command exists .
514
+ * @param {string } name - The process name to check.
515
+ * @returns {boolean } Returns true if the process runs .
444
516
*/
445
- const commandExists = ( cmd ) => {
517
+ const processRuns = ( name ) => {
446
518
try {
447
- return ! ! process . execSync ( `which ${ cmd } ` , { encoding : "utf8" } ) ;
519
+ return ! ! process . execSync ( `pidof ${ name } ` , { encoding : "utf8" } ) ;
520
+ } catch { }
521
+ return false ;
522
+ } ;
523
+
524
+ /**
525
+ * Checks if a command is available using `which`.
526
+ *
527
+ * @param {string } name - The command name to check.
528
+ * @returns {boolean } Returns true if the command is available.
529
+ */
530
+ const commandExists = ( name ) => {
531
+ try {
532
+ return ! ! process . execSync ( `which ${ name } ` , { encoding : "utf8" } ) ;
448
533
} catch { }
449
534
return false ;
450
535
} ;
@@ -499,6 +584,81 @@ const execAsyncCommand = (cmd, args = [], callback = null) => {
499
584
}
500
585
} catch ( error ) {
501
586
console . error ( "Execute Async:" , error . message ) ;
587
+ if ( typeof callback === "function" ) {
588
+ callback ( null , error . message ) ;
589
+ }
590
+ }
591
+ } ) ;
592
+ return proc ;
593
+ } ;
594
+
595
+ /**
596
+ * Executes a D-Bus method call synchronously using `dbus-send`.
597
+ *
598
+ * @param {string } path - The D-Bus object path.
599
+ * @param {string } method - The D-Bus method name.
600
+ * @param {Array<string> } values - The argument values for the D-Bus method.
601
+ * @param {function } callback - A callback function that receives the output or error.
602
+ */
603
+ const dbusCall = ( path , method , values , callback = null ) => {
604
+ const cmd = "dbus-send" ;
605
+ const iface = path . slice ( 1 ) . replace ( / \/ / g, "." ) ;
606
+ const dest = `${ iface } ${ path } ${ iface } .${ method } ${ values . join ( " " ) } ` ;
607
+ const args = [ "--print-reply" , "--type=method_call" , `--dest=${ dest } ` ] ;
608
+ try {
609
+ const output = process . execSync ( [ cmd , ...args ] . join ( " " ) , { encoding : "utf8" } ) ;
610
+ if ( typeof callback === "function" ) {
611
+ callback ( output . trim ( ) . replace ( / \0 / g, "" ) , null ) ;
612
+ }
613
+ } catch ( error ) {
614
+ console . error ( "Call D-Bus:" , error . message ) ;
615
+ if ( typeof callback === "function" ) {
616
+ callback ( null , error . message ) ;
617
+ }
618
+ }
619
+ } ;
620
+
621
+ /**
622
+ * Monitors D-Bus property changes asynchronously using `dbus-monitor`.
623
+ *
624
+ * @param {string } path - The D-Bus object path.
625
+ * @param {function } callback - A callback function that receives the changed property.
626
+ * @returns {object } The spawned process object.
627
+ */
628
+ const dbusMonitor = ( path , callback ) => {
629
+ const cmd = "dbus-monitor" ;
630
+ const args = [ `interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',path='${ path } '` ] ;
631
+ const proc = process . spawn ( cmd , args ) ;
632
+ proc . stdout . on ( "data" , ( data ) => {
633
+ try {
634
+ const signal = data . toString ( ) ;
635
+ if ( signal . includes ( "member=PropertiesChanged" ) && signal . includes ( `path=${ path } ` ) ) {
636
+ const dicts = [ ...signal . matchAll ( / d i c t e n t r y \( \s * ( [ ^ ) ] * ?) \) / g) ] . map ( ( dict ) => dict [ 1 ] . trim ( ) ) ;
637
+ if ( dicts . length ) {
638
+ dicts . forEach ( ( dict ) => {
639
+ const key = dict . match ( / s t r i n g " ( .* ?) " / ) ;
640
+ const value = dict . match ( / (?< = v a r i a n t \s + ) ( .* ) / ) ;
641
+ if ( key && value && typeof callback === "function" ) {
642
+ callback ( { [ key [ 1 ] . trim ( ) ] : value [ 1 ] . trim ( ) . split ( " " ) . pop ( ) } , null ) ;
643
+ }
644
+ } ) ;
645
+ } else {
646
+ callback ( { Visible : `${ HARDWARE . keyboard . visibility } ` } , null ) ;
647
+ }
648
+ }
649
+ } catch ( error ) {
650
+ console . error ( "Monitor D-Bus:" , error . message ) ;
651
+ if ( typeof callback === "function" ) {
652
+ callback ( null , error . message ) ;
653
+ }
654
+ }
655
+ } ) ;
656
+ proc . stderr . on ( "data" , ( data ) => {
657
+ if ( data ) {
658
+ console . error ( "Monitor D-Bus:" , data . toString ( ) ) ;
659
+ if ( typeof callback === "function" ) {
660
+ callback ( null , data . toString ( ) ) ;
661
+ }
502
662
}
503
663
} ) ;
504
664
return proc ;
@@ -524,6 +684,9 @@ module.exports = {
524
684
getDisplayBrightnessMax,
525
685
getDisplayBrightness,
526
686
setDisplayBrightness,
687
+ getKeyboardVisibility,
688
+ setKeyboardVisibility,
689
+ checkPackageUpgrades,
527
690
shutdownSystem,
528
691
rebootSystem,
529
692
} ;
0 commit comments