2020-03-03 14:36:29 +01:00
/*************************************************************************/
/* os_linuxbsd.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
2022-01-03 21:27:34 +01:00
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
2020-03-03 14:36:29 +01:00
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
# include "os_linuxbsd.h"
2021-06-11 14:51:48 +02:00
# include "core/io/dir_access.h"
2020-06-29 13:29:31 +02:00
# include "main/main.h"
2022-03-09 09:58:20 +01:00
# include "servers/display_server.h"
2020-06-29 13:29:31 +02:00
2022-10-11 12:39:41 +02:00
# include "modules/modules_enabled.gen.h" // For regex.
# ifdef MODULE_REGEX_ENABLED
# include "modules/regex/regex.h"
# endif
2020-06-29 13:29:31 +02:00
# ifdef X11_ENABLED
# include "display_server_x11.h"
# endif
2020-03-03 14:36:29 +01:00
# ifdef HAVE_MNTENT
# include <mntent.h>
# endif
2022-10-03 11:43:20 +02:00
# include <dlfcn.h>
2020-03-03 14:36:29 +01:00
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <sys/stat.h>
# include <sys/types.h>
2022-09-16 11:14:14 +02:00
# include <sys/utsname.h>
2020-03-03 14:36:29 +01:00
# include <unistd.h>
2022-07-08 14:38:30 +02:00
# ifdef FONTCONFIG_ENABLED
2022-07-27 20:07:21 +02:00
# include "fontconfig-so_wrap.h"
2022-07-08 14:38:30 +02:00
# endif
2021-07-22 18:23:48 +02:00
void OS_LinuxBSD : : alert ( const String & p_alert , const String & p_title ) {
const char * message_programs [ ] = { " zenity " , " kdialog " , " Xdialog " , " xmessage " } ;
String path = get_environment ( " PATH " ) ;
Vector < String > path_elems = path . split ( " : " , false ) ;
String program ;
for ( int i = 0 ; i < path_elems . size ( ) ; i + + ) {
for ( uint64_t k = 0 ; k < sizeof ( message_programs ) / sizeof ( char * ) ; k + + ) {
2022-08-30 02:34:01 +02:00
String tested_path = path_elems [ i ] . path_join ( message_programs [ k ] ) ;
2021-07-22 18:23:48 +02:00
if ( FileAccess : : exists ( tested_path ) ) {
program = tested_path ;
break ;
}
}
if ( program . length ( ) ) {
break ;
}
}
List < String > args ;
if ( program . ends_with ( " zenity " ) ) {
args . push_back ( " --error " ) ;
args . push_back ( " --width " ) ;
args . push_back ( " 500 " ) ;
args . push_back ( " --title " ) ;
args . push_back ( p_title ) ;
args . push_back ( " --text " ) ;
args . push_back ( p_alert ) ;
}
if ( program . ends_with ( " kdialog " ) ) {
args . push_back ( " --error " ) ;
args . push_back ( p_alert ) ;
args . push_back ( " --title " ) ;
args . push_back ( p_title ) ;
}
if ( program . ends_with ( " Xdialog " ) ) {
args . push_back ( " --title " ) ;
args . push_back ( p_title ) ;
args . push_back ( " --msgbox " ) ;
args . push_back ( p_alert ) ;
args . push_back ( " 0 " ) ;
args . push_back ( " 0 " ) ;
}
if ( program . ends_with ( " xmessage " ) ) {
args . push_back ( " -center " ) ;
args . push_back ( " -title " ) ;
args . push_back ( p_title ) ;
args . push_back ( p_alert ) ;
}
if ( program . length ( ) ) {
execute ( program , args ) ;
} else {
print_line ( p_alert ) ;
}
}
2020-03-03 14:36:29 +01:00
void OS_LinuxBSD : : initialize ( ) {
crash_handler . initialize ( ) ;
OS_Unix : : initialize_core ( ) ;
}
void OS_LinuxBSD : : initialize_joypads ( ) {
# ifdef JOYDEV_ENABLED
2020-04-28 15:19:37 +02:00
joypad = memnew ( JoypadLinux ( Input : : get_singleton ( ) ) ) ;
2020-03-03 14:36:29 +01:00
# endif
}
String OS_LinuxBSD : : get_unique_id ( ) const {
static String machine_id ;
2020-12-15 13:04:21 +01:00
if ( machine_id . is_empty ( ) ) {
2022-03-23 10:08:58 +01:00
Ref < FileAccess > f = FileAccess : : open ( " /etc/machine-id " , FileAccess : : READ ) ;
if ( f . is_valid ( ) ) {
2020-12-15 13:04:21 +01:00
while ( machine_id . is_empty ( ) & & ! f - > eof_reached ( ) ) {
2020-03-03 14:36:29 +01:00
machine_id = f - > get_line ( ) . strip_edges ( ) ;
}
}
}
return machine_id ;
}
2020-12-27 01:50:21 +01:00
String OS_LinuxBSD : : get_processor_name ( ) const {
2022-03-23 10:08:58 +01:00
Ref < FileAccess > f = FileAccess : : open ( " /proc/cpuinfo " , FileAccess : : READ ) ;
ERR_FAIL_COND_V_MSG ( f . is_null ( ) , " " , String ( " Couldn't open `/proc/cpuinfo` to get the CPU model name. Returning an empty string. " ) ) ;
2020-12-27 01:50:21 +01:00
while ( ! f - > eof_reached ( ) ) {
const String line = f - > get_line ( ) ;
if ( line . find ( " model name " ) ! = - 1 ) {
return line . split ( " : " ) [ 1 ] . strip_edges ( ) ;
}
}
ERR_FAIL_V_MSG ( " " , String ( " Couldn't get the CPU model name from `/proc/cpuinfo`. Returning an empty string. " ) ) ;
}
2020-03-03 14:36:29 +01:00
void OS_LinuxBSD : : finalize ( ) {
2020-05-14 16:41:43 +02:00
if ( main_loop ) {
2020-03-03 14:36:29 +01:00
memdelete ( main_loop ) ;
2020-05-14 16:41:43 +02:00
}
2020-04-02 01:20:12 +02:00
main_loop = nullptr ;
2020-03-03 14:36:29 +01:00
# ifdef ALSAMIDI_ENABLED
driver_alsamidi . close ( ) ;
# endif
# ifdef JOYDEV_ENABLED
2020-08-02 20:30:56 +02:00
if ( joypad ) {
memdelete ( joypad ) ;
}
2020-03-03 14:36:29 +01:00
# endif
}
MainLoop * OS_LinuxBSD : : get_main_loop ( ) const {
return main_loop ;
}
void OS_LinuxBSD : : delete_main_loop ( ) {
2020-05-14 16:41:43 +02:00
if ( main_loop ) {
2020-03-03 14:36:29 +01:00
memdelete ( main_loop ) ;
2020-05-14 16:41:43 +02:00
}
2020-04-02 01:20:12 +02:00
main_loop = nullptr ;
2020-03-03 14:36:29 +01:00
}
void OS_LinuxBSD : : set_main_loop ( MainLoop * p_main_loop ) {
main_loop = p_main_loop ;
}
String OS_LinuxBSD : : get_name ( ) const {
# ifdef __linux__
return " Linux " ;
# elif defined(__FreeBSD__)
return " FreeBSD " ;
# elif defined(__NetBSD__)
return " NetBSD " ;
2021-05-21 21:35:31 +02:00
# elif defined(__OpenBSD__)
return " OpenBSD " ;
2020-03-03 14:36:29 +01:00
# else
return " BSD " ;
# endif
}
2022-09-16 11:14:14 +02:00
String OS_LinuxBSD : : get_systemd_os_release_info_value ( const String & key ) const {
static String info ;
if ( info . is_empty ( ) ) {
Ref < FileAccess > f = FileAccess : : open ( " /etc/os-release " , FileAccess : : READ ) ;
if ( f . is_valid ( ) ) {
while ( ! f - > eof_reached ( ) ) {
const String line = f - > get_line ( ) ;
if ( line . find ( key ) ! = - 1 ) {
return line . split ( " = " ) [ 1 ] . strip_edges ( ) ;
}
}
}
}
return info ;
}
String OS_LinuxBSD : : get_distribution_name ( ) const {
static String systemd_name = get_systemd_os_release_info_value ( " NAME " ) ; // returns a value for systemd users, otherwise an empty string.
if ( ! systemd_name . is_empty ( ) ) {
return systemd_name ;
}
struct utsname uts ; // returns a decent value for BSD family.
uname ( & uts ) ;
return uts . sysname ;
}
String OS_LinuxBSD : : get_version ( ) const {
static String systemd_version = get_systemd_os_release_info_value ( " VERSION " ) ; // returns a value for systemd users, otherwise an empty string.
if ( ! systemd_version . is_empty ( ) ) {
return systemd_version ;
}
struct utsname uts ; // returns a decent value for BSD family.
uname ( & uts ) ;
return uts . version ;
}
2022-10-11 12:39:41 +02:00
Vector < String > OS_LinuxBSD : : get_video_adapter_driver_info ( ) const {
const String rendering_device_name = RenderingServer : : get_singleton ( ) - > get_rendering_device ( ) - > get_device_name ( ) ; // e.g. `NVIDIA GeForce GTX 970`
const String rendering_device_vendor = RenderingServer : : get_singleton ( ) - > get_rendering_device ( ) - > get_device_vendor_name ( ) ; // e.g. `NVIDIA`
const String card_name = rendering_device_name . trim_prefix ( rendering_device_vendor ) . strip_edges ( ) ; // -> `GeForce GTX 970`
String vendor_device_id_mappings ;
List < String > lspci_args ;
lspci_args . push_back ( " -n " ) ;
Error err = const_cast < OS_LinuxBSD * > ( this ) - > execute ( " lspci " , lspci_args , & vendor_device_id_mappings ) ;
if ( err ! = OK | | vendor_device_id_mappings . is_empty ( ) ) {
return Vector < String > ( ) ;
}
// Usually found under "VGA", but for example NVIDIA mobile/laptop adapters are often listed under "3D" and some AMD adapters are under "Display".
const String dc_vga = " 0300 " ; // VGA compatible controller
const String dc_display = " 0302 " ; // Display controller
const String dc_3d = " 0380 " ; // 3D controller
// splitting results by device class allows prioritizing, if multiple devices are found.
Vector < String > class_vga_device_candidates ;
Vector < String > class_display_device_candidates ;
Vector < String > class_3d_device_candidates ;
# ifdef MODULE_REGEX_ENABLED
RegEx regex_id_format = RegEx ( ) ;
regex_id_format . compile ( " ^[a-f0-9]{4}:[a-f0-9]{4}$ " ) ; // e.g. `10de:13c2`; IDs are always in hexadecimal
# endif
Vector < String > value_lines = vendor_device_id_mappings . split ( " \n " , false ) ; // example: `02:00.0 0300: 10de:13c2 (rev a1)`
for ( const String & line : value_lines ) {
Vector < String > columns = line . split ( " " , false ) ;
if ( columns . size ( ) < 3 ) {
continue ;
}
String device_class = columns [ 1 ] . trim_suffix ( " : " ) ;
String vendor_device_id_mapping = columns [ 2 ] ;
# ifdef MODULE_REGEX_ENABLED
if ( regex_id_format . search ( vendor_device_id_mapping ) . is_null ( ) ) {
continue ;
}
# endif
if ( device_class = = dc_vga ) {
class_vga_device_candidates . push_back ( vendor_device_id_mapping ) ;
} else if ( device_class = = dc_display ) {
class_display_device_candidates . push_back ( vendor_device_id_mapping ) ;
} else if ( device_class = = dc_3d ) {
class_3d_device_candidates . push_back ( vendor_device_id_mapping ) ;
}
}
// Check results against currently used device (`card_name`), in case the user has multiple graphics cards.
const String device_lit = " Device " ; // line of interest
class_vga_device_candidates = OS_LinuxBSD : : lspci_device_filter ( class_vga_device_candidates , dc_vga , device_lit , card_name ) ;
class_display_device_candidates = OS_LinuxBSD : : lspci_device_filter ( class_display_device_candidates , dc_display , device_lit , card_name ) ;
class_3d_device_candidates = OS_LinuxBSD : : lspci_device_filter ( class_3d_device_candidates , dc_3d , device_lit , card_name ) ;
// Get driver names and filter out invalid ones, because some adapters are dummys used only for passthrough.
// And they have no indicator besides certain driver names.
const String kernel_lit = " Kernel driver in use " ; // line of interest
const String dummys = " vfio " ; // for e.g. pci passthrough dummy kernel driver `vfio-pci`
Vector < String > class_vga_device_drivers = OS_LinuxBSD : : lspci_get_device_value ( class_vga_device_candidates , kernel_lit , dummys ) ;
Vector < String > class_display_device_drivers = OS_LinuxBSD : : lspci_get_device_value ( class_display_device_candidates , kernel_lit , dummys ) ;
Vector < String > class_3d_device_drivers = OS_LinuxBSD : : lspci_get_device_value ( class_3d_device_candidates , kernel_lit , dummys ) ;
static String driver_name ;
static String driver_version ;
// Use first valid value:
for ( const String & driver : class_3d_device_drivers ) {
driver_name = driver ;
break ;
}
if ( driver_name . is_empty ( ) ) {
for ( const String & driver : class_display_device_drivers ) {
driver_name = driver ;
break ;
}
}
if ( driver_name . is_empty ( ) ) {
for ( const String & driver : class_vga_device_drivers ) {
driver_name = driver ;
break ;
}
}
Vector < String > info ;
info . push_back ( driver_name ) ;
String modinfo ;
List < String > modinfo_args ;
modinfo_args . push_back ( driver_name ) ;
err = const_cast < OS_LinuxBSD * > ( this ) - > execute ( " modinfo " , modinfo_args , & modinfo ) ;
if ( err ! = OK | | modinfo . is_empty ( ) ) {
info . push_back ( " " ) ; // So that this method always either returns an empty array, or an array of length 2.
return info ;
}
Vector < String > lines = modinfo . split ( " \n " , false ) ;
for ( const String & line : lines ) {
Vector < String > columns = line . split ( " : " , false , 1 ) ;
if ( columns . size ( ) < 2 ) {
continue ;
}
if ( columns [ 0 ] . strip_edges ( ) = = " version " ) {
driver_version = columns [ 1 ] . strip_edges ( ) ; // example value: `510.85.02` on Linux/BSD
break ;
}
}
info . push_back ( driver_version ) ;
return info ;
}
Vector < String > OS_LinuxBSD : : lspci_device_filter ( Vector < String > vendor_device_id_mapping , String class_suffix , String check_column , String whitelist ) const {
// NOTE: whitelist can be changed to `Vector<String>`, if the need arises.
const String sep = " : " ;
Vector < String > devices ;
for ( const String & mapping : vendor_device_id_mapping ) {
String device ;
List < String > d_args ;
d_args . push_back ( " -d " ) ;
d_args . push_back ( mapping + sep + class_suffix ) ;
d_args . push_back ( " -vmm " ) ;
Error err = const_cast < OS_LinuxBSD * > ( this ) - > execute ( " lspci " , d_args , & device ) ; // e.g. `lspci -d 10de:13c2:0300 -vmm`
if ( err ! = OK ) {
return Vector < String > ( ) ;
} else if ( device . is_empty ( ) ) {
continue ;
}
Vector < String > device_lines = device . split ( " \n " , false ) ;
for ( const String & line : device_lines ) {
Vector < String > columns = line . split ( " : " , false , 1 ) ;
if ( columns . size ( ) < 2 ) {
continue ;
}
if ( columns [ 0 ] . strip_edges ( ) = = check_column ) {
// for `column[0] == "Device"` this may contain `GM204 [GeForce GTX 970]`
bool is_valid = true ;
if ( ! whitelist . is_empty ( ) ) {
is_valid = columns [ 1 ] . strip_edges ( ) . contains ( whitelist ) ;
}
if ( is_valid ) {
devices . push_back ( mapping ) ;
}
break ;
}
}
}
return devices ;
}
Vector < String > OS_LinuxBSD : : lspci_get_device_value ( Vector < String > vendor_device_id_mapping , String check_column , String blacklist ) const {
// NOTE: blacklist can be changed to `Vector<String>`, if the need arises.
const String sep = " : " ;
Vector < String > values ;
for ( const String & mapping : vendor_device_id_mapping ) {
String device ;
List < String > d_args ;
d_args . push_back ( " -d " ) ;
d_args . push_back ( mapping ) ;
d_args . push_back ( " -k " ) ;
Error err = const_cast < OS_LinuxBSD * > ( this ) - > execute ( " lspci " , d_args , & device ) ; // e.g. `lspci -d 10de:13c2 -k`
if ( err ! = OK ) {
return Vector < String > ( ) ;
} else if ( device . is_empty ( ) ) {
continue ;
}
Vector < String > device_lines = device . split ( " \n " , false ) ;
for ( const String & line : device_lines ) {
Vector < String > columns = line . split ( " : " , false , 1 ) ;
if ( columns . size ( ) < 2 ) {
continue ;
}
if ( columns [ 0 ] . strip_edges ( ) = = check_column ) {
// for `column[0] == "Kernel driver in use"` this may contain `nvidia`
bool is_valid = true ;
const String value = columns [ 1 ] . strip_edges ( ) ;
if ( ! blacklist . is_empty ( ) ) {
is_valid = ! value . contains ( blacklist ) ;
}
if ( is_valid ) {
values . push_back ( value ) ;
}
break ;
}
}
}
return values ;
}
2020-03-03 14:36:29 +01:00
Error OS_LinuxBSD : : shell_open ( String p_uri ) {
Error ok ;
2020-10-24 17:54:24 +02:00
int err_code ;
2020-03-03 14:36:29 +01:00
List < String > args ;
args . push_back ( p_uri ) ;
2020-10-24 17:54:24 +02:00
// Agnostic
2020-12-18 19:49:13 +01:00
ok = execute ( " xdg-open " , args , nullptr , & err_code ) ;
2020-10-24 17:54:24 +02:00
if ( ok = = OK & & ! err_code ) {
return OK ;
} else if ( err_code = = 2 ) {
return ERR_FILE_NOT_FOUND ;
}
// GNOME
args . push_front ( " open " ) ; // The command is `gio open`, so we need to add it to args
2020-12-18 19:49:13 +01:00
ok = execute ( " gio " , args , nullptr , & err_code ) ;
2020-10-24 17:54:24 +02:00
if ( ok = = OK & & ! err_code ) {
return OK ;
} else if ( err_code = = 2 ) {
return ERR_FILE_NOT_FOUND ;
}
args . pop_front ( ) ;
2020-12-18 19:49:13 +01:00
ok = execute ( " gvfs-open " , args , nullptr , & err_code ) ;
2020-10-24 17:54:24 +02:00
if ( ok = = OK & & ! err_code ) {
2020-03-03 14:36:29 +01:00
return OK ;
2020-10-24 17:54:24 +02:00
} else if ( err_code = = 2 ) {
return ERR_FILE_NOT_FOUND ;
2020-05-14 16:41:43 +02:00
}
2020-10-24 17:54:24 +02:00
// KDE
2020-12-18 19:49:13 +01:00
ok = execute ( " kde-open5 " , args , nullptr , & err_code ) ;
2020-10-24 17:54:24 +02:00
if ( ok = = OK & & ! err_code ) {
2020-03-03 14:36:29 +01:00
return OK ;
2020-05-14 16:41:43 +02:00
}
2020-12-18 19:49:13 +01:00
ok = execute ( " kde-open " , args , nullptr , & err_code ) ;
2020-10-24 17:54:24 +02:00
return ! err_code ? ok : FAILED ;
2020-03-03 14:36:29 +01:00
}
bool OS_LinuxBSD : : _check_internal_feature_support ( const String & p_feature ) {
return p_feature = = " pc " ;
}
2021-12-20 10:28:54 +01:00
uint64_t OS_LinuxBSD : : get_embedded_pck_offset ( ) const {
Ref < FileAccess > f = FileAccess : : open ( get_executable_path ( ) , FileAccess : : READ ) ;
if ( f . is_null ( ) ) {
return 0 ;
}
// Read and check ELF magic number.
{
uint32_t magic = f - > get_32 ( ) ;
if ( magic ! = 0x464c457f ) { // 0x7F + "ELF"
return 0 ;
}
}
// Read program architecture bits from class field.
int bits = f - > get_8 ( ) * 32 ;
// Get info about the section header table.
int64_t section_table_pos ;
int64_t section_header_size ;
if ( bits = = 32 ) {
section_header_size = 40 ;
f - > seek ( 0x20 ) ;
section_table_pos = f - > get_32 ( ) ;
f - > seek ( 0x30 ) ;
} else { // 64
section_header_size = 64 ;
f - > seek ( 0x28 ) ;
section_table_pos = f - > get_64 ( ) ;
f - > seek ( 0x3c ) ;
}
int num_sections = f - > get_16 ( ) ;
int string_section_idx = f - > get_16 ( ) ;
// Load the strings table.
uint8_t * strings ;
{
// Jump to the strings section header.
f - > seek ( section_table_pos + string_section_idx * section_header_size ) ;
// Read strings data size and offset.
int64_t string_data_pos ;
int64_t string_data_size ;
if ( bits = = 32 ) {
f - > seek ( f - > get_position ( ) + 0x10 ) ;
string_data_pos = f - > get_32 ( ) ;
string_data_size = f - > get_32 ( ) ;
} else { // 64
f - > seek ( f - > get_position ( ) + 0x18 ) ;
string_data_pos = f - > get_64 ( ) ;
string_data_size = f - > get_64 ( ) ;
}
// Read strings data.
f - > seek ( string_data_pos ) ;
strings = ( uint8_t * ) memalloc ( string_data_size ) ;
if ( ! strings ) {
return 0 ;
}
f - > get_buffer ( strings , string_data_size ) ;
}
// Search for the "pck" section.
int64_t off = 0 ;
for ( int i = 0 ; i < num_sections ; + + i ) {
int64_t section_header_pos = section_table_pos + i * section_header_size ;
f - > seek ( section_header_pos ) ;
uint32_t name_offset = f - > get_32 ( ) ;
if ( strcmp ( ( char * ) strings + name_offset , " pck " ) = = 0 ) {
if ( bits = = 32 ) {
f - > seek ( section_header_pos + 0x10 ) ;
off = f - > get_32 ( ) ;
} else { // 64
f - > seek ( section_header_pos + 0x18 ) ;
off = f - > get_64 ( ) ;
}
break ;
}
}
memfree ( strings ) ;
return off ;
}
2022-07-08 14:38:30 +02:00
Vector < String > OS_LinuxBSD : : get_system_fonts ( ) const {
# ifdef FONTCONFIG_ENABLED
2022-07-27 20:07:21 +02:00
if ( ! font_config_initialized ) {
ERR_FAIL_V_MSG ( Vector < String > ( ) , " Unable to load fontconfig, system font support is disabled. " ) ;
}
2022-07-08 14:38:30 +02:00
HashSet < String > font_names ;
Vector < String > ret ;
FcConfig * config = FcInitLoadConfigAndFonts ( ) ;
ERR_FAIL_COND_V ( ! config , ret ) ;
FcObjectSet * object_set = FcObjectSetBuild ( FC_FAMILY , nullptr ) ;
ERR_FAIL_COND_V ( ! object_set , ret ) ;
static const char * allowed_formats [ ] = { " TrueType " , " CFF " } ;
for ( size_t i = 0 ; i < sizeof ( allowed_formats ) / sizeof ( const char * ) ; i + + ) {
FcPattern * pattern = FcPatternCreate ( ) ;
ERR_CONTINUE ( ! pattern ) ;
FcPatternAddBool ( pattern , FC_SCALABLE , FcTrue ) ;
FcPatternAddString ( pattern , FC_FONTFORMAT , reinterpret_cast < const FcChar8 * > ( allowed_formats [ i ] ) ) ;
FcFontSet * font_set = FcFontList ( config , pattern , object_set ) ;
if ( font_set ) {
for ( int j = 0 ; j < font_set - > nfont ; j + + ) {
char * family_name = nullptr ;
if ( FcPatternGetString ( font_set - > fonts [ j ] , FC_FAMILY , 0 , reinterpret_cast < FcChar8 * * > ( & family_name ) ) = = FcResultMatch ) {
if ( family_name ) {
font_names . insert ( String : : utf8 ( family_name ) ) ;
}
}
}
FcFontSetDestroy ( font_set ) ;
}
FcPatternDestroy ( pattern ) ;
}
FcObjectSetDestroy ( object_set ) ;
2022-08-09 11:57:53 +02:00
FcConfigDestroy ( config ) ;
2022-07-08 14:38:30 +02:00
for ( const String & E : font_names ) {
ret . push_back ( E ) ;
}
return ret ;
# else
2022-07-27 16:21:04 +02:00
ERR_FAIL_V_MSG ( Vector < String > ( ) , " Godot was compiled without fontconfig, system font support is disabled. " ) ;
2022-07-08 14:38:30 +02:00
# endif
}
String OS_LinuxBSD : : get_system_font_path ( const String & p_font_name , bool p_bold , bool p_italic ) const {
# ifdef FONTCONFIG_ENABLED
2022-07-27 20:07:21 +02:00
if ( ! font_config_initialized ) {
ERR_FAIL_V_MSG ( String ( ) , " Unable to load fontconfig, system font support is disabled. " ) ;
}
2022-07-08 14:38:30 +02:00
String ret ;
FcConfig * config = FcInitLoadConfigAndFonts ( ) ;
ERR_FAIL_COND_V ( ! config , ret ) ;
FcObjectSet * object_set = FcObjectSetBuild ( FC_FAMILY , FC_FILE , nullptr ) ;
ERR_FAIL_COND_V ( ! object_set , ret ) ;
FcPattern * pattern = FcPatternCreate ( ) ;
if ( pattern ) {
FcPatternAddBool ( pattern , FC_SCALABLE , FcTrue ) ;
FcPatternAddString ( pattern , FC_FAMILY , reinterpret_cast < const FcChar8 * > ( p_font_name . utf8 ( ) . get_data ( ) ) ) ;
FcPatternAddInteger ( pattern , FC_WEIGHT , p_bold ? FC_WEIGHT_BOLD : FC_WEIGHT_NORMAL ) ;
FcPatternAddInteger ( pattern , FC_SLANT , p_italic ? FC_SLANT_ITALIC : FC_SLANT_ROMAN ) ;
FcConfigSubstitute ( 0 , pattern , FcMatchPattern ) ;
FcDefaultSubstitute ( pattern ) ;
FcResult result ;
FcPattern * match = FcFontMatch ( 0 , pattern , & result ) ;
if ( match ) {
char * file_name = nullptr ;
if ( FcPatternGetString ( match , FC_FILE , 0 , reinterpret_cast < FcChar8 * * > ( & file_name ) ) = = FcResultMatch ) {
if ( file_name ) {
ret = String : : utf8 ( file_name ) ;
}
}
FcPatternDestroy ( match ) ;
}
FcPatternDestroy ( pattern ) ;
}
FcObjectSetDestroy ( object_set ) ;
2022-08-09 11:57:53 +02:00
FcConfigDestroy ( config ) ;
2022-07-27 16:21:04 +02:00
return ret ;
2022-07-08 14:38:30 +02:00
# else
2022-07-27 16:21:04 +02:00
ERR_FAIL_V_MSG ( String ( ) , " Godot was compiled without fontconfig, system font support is disabled. " ) ;
2022-07-08 14:38:30 +02:00
# endif
}
2020-03-03 14:36:29 +01:00
String OS_LinuxBSD : : get_config_path ( ) const {
if ( has_environment ( " XDG_CONFIG_HOME " ) ) {
2021-06-03 15:41:22 +02:00
if ( get_environment ( " XDG_CONFIG_HOME " ) . is_absolute_path ( ) ) {
2021-05-07 19:02:35 +02:00
return get_environment ( " XDG_CONFIG_HOME " ) ;
} else {
WARN_PRINT_ONCE ( " `XDG_CONFIG_HOME` is a relative path. Ignoring its value and falling back to `$HOME/.config` or `.` per the XDG Base Directory specification. " ) ;
2022-08-30 02:34:01 +02:00
return has_environment ( " HOME " ) ? get_environment ( " HOME " ) . path_join ( " .config " ) : " . " ;
2021-05-07 19:02:35 +02:00
}
2020-03-03 14:36:29 +01:00
} else if ( has_environment ( " HOME " ) ) {
2022-08-30 02:34:01 +02:00
return get_environment ( " HOME " ) . path_join ( " .config " ) ;
2020-03-03 14:36:29 +01:00
} else {
return " . " ;
}
}
String OS_LinuxBSD : : get_data_path ( ) const {
if ( has_environment ( " XDG_DATA_HOME " ) ) {
2021-06-03 15:41:22 +02:00
if ( get_environment ( " XDG_DATA_HOME " ) . is_absolute_path ( ) ) {
2021-05-07 19:02:35 +02:00
return get_environment ( " XDG_DATA_HOME " ) ;
} else {
WARN_PRINT_ONCE ( " `XDG_DATA_HOME` is a relative path. Ignoring its value and falling back to `$HOME/.local/share` or `get_config_path()` per the XDG Base Directory specification. " ) ;
2022-08-30 02:34:01 +02:00
return has_environment ( " HOME " ) ? get_environment ( " HOME " ) . path_join ( " .local/share " ) : get_config_path ( ) ;
2021-05-07 19:02:35 +02:00
}
2020-03-03 14:36:29 +01:00
} else if ( has_environment ( " HOME " ) ) {
2022-08-30 02:34:01 +02:00
return get_environment ( " HOME " ) . path_join ( " .local/share " ) ;
2020-03-03 14:36:29 +01:00
} else {
return get_config_path ( ) ;
}
}
String OS_LinuxBSD : : get_cache_path ( ) const {
if ( has_environment ( " XDG_CACHE_HOME " ) ) {
2021-06-03 15:41:22 +02:00
if ( get_environment ( " XDG_CACHE_HOME " ) . is_absolute_path ( ) ) {
2021-05-07 19:02:35 +02:00
return get_environment ( " XDG_CACHE_HOME " ) ;
} else {
WARN_PRINT_ONCE ( " `XDG_CACHE_HOME` is a relative path. Ignoring its value and falling back to `$HOME/.cache` or `get_config_path()` per the XDG Base Directory specification. " ) ;
2022-08-30 02:34:01 +02:00
return has_environment ( " HOME " ) ? get_environment ( " HOME " ) . path_join ( " .cache " ) : get_config_path ( ) ;
2021-05-07 19:02:35 +02:00
}
2020-03-03 14:36:29 +01:00
} else if ( has_environment ( " HOME " ) ) {
2022-08-30 02:34:01 +02:00
return get_environment ( " HOME " ) . path_join ( " .cache " ) ;
2020-03-03 14:36:29 +01:00
} else {
return get_config_path ( ) ;
}
}
2021-07-11 03:39:31 +02:00
String OS_LinuxBSD : : get_system_dir ( SystemDir p_dir , bool p_shared_storage ) const {
2020-03-03 14:36:29 +01:00
String xdgparam ;
switch ( p_dir ) {
case SYSTEM_DIR_DESKTOP : {
xdgparam = " DESKTOP " ;
} break ;
case SYSTEM_DIR_DCIM : {
xdgparam = " PICTURES " ;
} break ;
case SYSTEM_DIR_DOCUMENTS : {
xdgparam = " DOCUMENTS " ;
} break ;
case SYSTEM_DIR_DOWNLOADS : {
xdgparam = " DOWNLOAD " ;
} break ;
case SYSTEM_DIR_MOVIES : {
xdgparam = " VIDEOS " ;
} break ;
case SYSTEM_DIR_MUSIC : {
xdgparam = " MUSIC " ;
} break ;
case SYSTEM_DIR_PICTURES : {
xdgparam = " PICTURES " ;
} break ;
case SYSTEM_DIR_RINGTONES : {
xdgparam = " MUSIC " ;
} break ;
}
String pipe ;
List < String > arg ;
arg . push_back ( xdgparam ) ;
2020-12-18 19:49:13 +01:00
Error err = const_cast < OS_LinuxBSD * > ( this ) - > execute ( " xdg-user-dir " , arg , & pipe ) ;
2020-05-14 16:41:43 +02:00
if ( err ! = OK ) {
2020-03-03 14:36:29 +01:00
return " . " ;
2020-05-14 16:41:43 +02:00
}
2020-03-03 14:36:29 +01:00
return pipe . strip_edges ( ) ;
}
void OS_LinuxBSD : : run ( ) {
2020-05-14 16:41:43 +02:00
if ( ! main_loop ) {
2020-03-03 14:36:29 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2020-03-03 14:36:29 +01:00
2020-12-22 10:50:29 +01:00
main_loop - > initialize ( ) ;
2020-03-03 14:36:29 +01:00
//uint64_t last_ticks=get_ticks_usec();
//int frames=0;
//uint64_t frame=0;
2022-08-16 20:29:55 +02:00
while ( true ) {
2020-03-03 14:36:29 +01:00
DisplayServer : : get_singleton ( ) - > process_events ( ) ; // get rid of pending events
# ifdef JOYDEV_ENABLED
joypad - > process_joypads ( ) ;
# endif
2020-05-14 16:41:43 +02:00
if ( Main : : iteration ( ) ) {
2020-03-03 14:36:29 +01:00
break ;
2020-05-14 16:41:43 +02:00
}
2022-02-16 13:56:32 +01:00
}
2020-03-03 14:36:29 +01:00
2020-12-22 10:50:29 +01:00
main_loop - > finalize ( ) ;
2020-03-03 14:36:29 +01:00
}
void OS_LinuxBSD : : disable_crash_handler ( ) {
crash_handler . disable ( ) ;
}
bool OS_LinuxBSD : : is_disable_crash_handler ( ) const {
return crash_handler . is_disabled ( ) ;
}
static String get_mountpoint ( const String & p_path ) {
struct stat s ;
if ( stat ( p_path . utf8 ( ) . get_data ( ) , & s ) ) {
return " " ;
}
# ifdef HAVE_MNTENT
dev_t dev = s . st_dev ;
FILE * fd = setmntent ( " /proc/mounts " , " r " ) ;
if ( ! fd ) {
return " " ;
}
struct mntent mnt ;
char buf [ 1024 ] ;
size_t buflen = 1024 ;
while ( getmntent_r ( fd , & mnt , buf , buflen ) ) {
if ( ! stat ( mnt . mnt_dir , & s ) & & s . st_dev = = dev ) {
endmntent ( fd ) ;
return String ( mnt . mnt_dir ) ;
}
}
endmntent ( fd ) ;
# endif
return " " ;
}
Error OS_LinuxBSD : : move_to_trash ( const String & p_path ) {
2022-02-20 01:58:38 +01:00
String path = p_path . rstrip ( " / " ) ; // Strip trailing slash when path points to a directory
2020-11-30 21:19:52 +01:00
int err_code ;
List < String > args ;
2022-02-20 01:58:38 +01:00
args . push_back ( path ) ;
2020-11-30 21:19:52 +01:00
args . push_front ( " trash " ) ; // The command is `gio trash <file_name>` so we need to add it to args.
2020-12-18 19:49:13 +01:00
Error result = execute ( " gio " , args , nullptr , & err_code ) ; // For GNOME based machines.
2020-11-30 21:19:52 +01:00
if ( result = = OK & & ! err_code ) {
return OK ;
} else if ( err_code = = 2 ) {
return ERR_FILE_NOT_FOUND ;
}
args . pop_front ( ) ;
args . push_front ( " move " ) ;
args . push_back ( " trash:/ " ) ; // The command is `kioclient5 move <file_name> trash:/`.
2020-12-18 19:49:13 +01:00
result = execute ( " kioclient5 " , args , nullptr , & err_code ) ; // For KDE based machines.
2020-11-30 21:19:52 +01:00
if ( result = = OK & & ! err_code ) {
return OK ;
} else if ( err_code = = 2 ) {
return ERR_FILE_NOT_FOUND ;
}
args . pop_front ( ) ;
args . pop_back ( ) ;
2020-12-18 19:49:13 +01:00
result = execute ( " gvfs-trash " , args , nullptr , & err_code ) ; // For older Linux machines.
2020-11-30 21:19:52 +01:00
if ( result = = OK & & ! err_code ) {
return OK ;
} else if ( err_code = = 2 ) {
return ERR_FILE_NOT_FOUND ;
}
// If the commands `kioclient5`, `gio` or `gvfs-trash` don't exist on the system we do it manually.
String trash_path = " " ;
2022-02-20 01:58:38 +01:00
String mnt = get_mountpoint ( path ) ;
2020-03-03 14:36:29 +01:00
2020-11-30 21:19:52 +01:00
// If there is a directory "[Mountpoint]/.Trash-[UID], use it as the trash can.
2021-12-09 10:42:46 +01:00
if ( ! mnt . is_empty ( ) ) {
2022-02-20 01:58:38 +01:00
String mountpoint_trash_path ( mnt + " /.Trash- " + itos ( getuid ( ) ) ) ;
2020-03-03 14:36:29 +01:00
struct stat s ;
2022-02-20 01:58:38 +01:00
if ( ! stat ( mountpoint_trash_path . utf8 ( ) . get_data ( ) , & s ) ) {
trash_path = mountpoint_trash_path ;
2020-03-03 14:36:29 +01:00
}
}
2020-11-30 21:19:52 +01:00
// Otherwise, if ${XDG_DATA_HOME} is defined, use "${XDG_DATA_HOME}/Trash" as the trash can.
2021-12-09 10:42:46 +01:00
if ( trash_path . is_empty ( ) ) {
2020-03-03 14:36:29 +01:00
char * dhome = getenv ( " XDG_DATA_HOME " ) ;
if ( dhome ) {
2022-01-06 10:34:10 +01:00
trash_path = String : : utf8 ( dhome ) + " /Trash " ;
2020-03-03 14:36:29 +01:00
}
}
2020-11-30 21:19:52 +01:00
// Otherwise, if ${HOME} is defined, use "${HOME}/.local/share/Trash" as the trash can.
2021-12-09 10:42:46 +01:00
if ( trash_path . is_empty ( ) ) {
2020-03-03 14:36:29 +01:00
char * home = getenv ( " HOME " ) ;
if ( home ) {
2022-01-06 10:34:10 +01:00
trash_path = String : : utf8 ( home ) + " /.local/share/Trash " ;
2020-03-03 14:36:29 +01:00
}
}
// Issue an error if none of the previous locations is appropriate for the trash can.
2021-12-09 10:42:46 +01:00
ERR_FAIL_COND_V_MSG ( trash_path . is_empty ( ) , FAILED , " Could not determine the trash can location " ) ;
2020-03-03 14:36:29 +01:00
// Create needed directories for decided trash can location.
2020-11-30 21:19:52 +01:00
{
2022-03-23 10:08:58 +01:00
Ref < DirAccess > dir_access = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
2020-11-30 21:19:52 +01:00
Error err = dir_access - > make_dir_recursive ( trash_path ) ;
Fix various typos with codespell
Found via `codespell -q 3 -S ./thirdparty,*.po,./DONORS.md -L ackward,ang,ans,ba,beng,cas,childs,childrens,dof,doubleclick,fave,findn,hist,inout,leapyear,lod,nd,numer,ois,ony,paket,seeked,sinc,switchs,te,uint`
2021-07-07 17:17:32 +02:00
// Issue an error if trash can is not created properly.
2020-11-30 21:19:52 +01:00
ERR_FAIL_COND_V_MSG ( err ! = OK , err , " Could not create the trash path \" " + trash_path + " \" " ) ;
err = dir_access - > make_dir_recursive ( trash_path + " /files " ) ;
2022-02-20 01:58:38 +01:00
ERR_FAIL_COND_V_MSG ( err ! = OK , err , " Could not create the trash path \" " + trash_path + " /files \" " ) ;
2020-11-30 21:19:52 +01:00
err = dir_access - > make_dir_recursive ( trash_path + " /info " ) ;
2022-02-20 01:58:38 +01:00
ERR_FAIL_COND_V_MSG ( err ! = OK , err , " Could not create the trash path \" " + trash_path + " /info \" " ) ;
2020-11-30 21:19:52 +01:00
}
2020-03-03 14:36:29 +01:00
2020-11-30 21:19:52 +01:00
// The trash can is successfully created, now we check that we don't exceed our file name length limit.
// If the file name is too long trim it so we can add the identifying number and ".trashinfo".
// Assumes that the file name length limit is 255 characters.
2022-02-20 01:58:38 +01:00
String file_name = path . get_file ( ) ;
2020-11-30 21:19:52 +01:00
if ( file_name . length ( ) > 240 ) {
file_name = file_name . substr ( 0 , file_name . length ( ) - 15 ) ;
}
String dest_path = trash_path + " /files/ " + file_name ;
struct stat buff ;
int id_number = 0 ;
String fn = file_name ;
// Checks if a resource with the same name already exist in the trash can,
// if there is, add an identifying number to our resource's name.
while ( stat ( dest_path . utf8 ( ) . get_data ( ) , & buff ) = = 0 ) {
id_number + + ;
// Added a limit to check for identically named files already on the trash can
// if there are too many it could make the editor unresponsive.
ERR_FAIL_COND_V_MSG ( id_number > 99 , FAILED , " Too many identically named resources already in the trash can. " ) ;
fn = file_name + " . " + itos ( id_number ) ;
dest_path = trash_path + " /files/ " + fn ;
}
file_name = fn ;
2022-02-20 01:58:38 +01:00
String renamed_path = path . get_base_dir ( ) + " / " + file_name ;
2020-11-30 21:19:52 +01:00
// Generates the .trashinfo file
2022-09-08 07:36:10 +02:00
OS : : DateTime dt = OS : : get_singleton ( ) - > get_datetime ( false ) ;
String timestamp = vformat ( " %04d-%02d-%02dT%02d:%02d: " , dt . year , ( int ) dt . month , dt . day , dt . hour , dt . minute ) ;
timestamp = vformat ( " %s%02d " , timestamp , dt . second ) ; // vformat only supports up to 6 arguments.
2022-02-20 01:58:38 +01:00
String trash_info = " [Trash Info] \n Path= " + path . uri_encode ( ) + " \n DeletionDate= " + timestamp + " \n " ;
2020-11-30 21:19:52 +01:00
{
Error err ;
2022-04-12 09:12:40 +02:00
{
Ref < FileAccess > file = FileAccess : : open ( trash_path + " /info/ " + file_name + " .trashinfo " , FileAccess : : WRITE , & err ) ;
ERR_FAIL_COND_V_MSG ( err ! = OK , err , " Can't create trashinfo file: \" " + trash_path + " /info/ " + file_name + " .trashinfo \" " ) ;
file - > store_string ( trash_info ) ;
}
2020-11-30 21:19:52 +01:00
// Rename our resource before moving it to the trash can.
2022-03-23 10:08:58 +01:00
Ref < DirAccess > dir_access = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
2022-02-20 01:58:38 +01:00
err = dir_access - > rename ( path , renamed_path ) ;
ERR_FAIL_COND_V_MSG ( err ! = OK , err , " Can't rename file \" " + path + " \" to \" " + renamed_path + " \" " ) ;
2020-03-03 14:36:29 +01:00
}
2020-11-30 21:19:52 +01:00
// Move the given resource to the trash can.
2020-03-03 14:36:29 +01:00
// Do not use DirAccess:rename() because it can't move files across multiple mountpoints.
List < String > mv_args ;
2022-02-20 01:58:38 +01:00
mv_args . push_back ( renamed_path ) ;
2020-11-30 21:19:52 +01:00
mv_args . push_back ( trash_path + " /files " ) ;
{
int retval ;
2020-12-18 19:49:13 +01:00
Error err = execute ( " mv " , mv_args , nullptr , & retval ) ;
2020-11-30 21:19:52 +01:00
// Issue an error if "mv" failed to move the given resource to the trash can.
if ( err ! = OK | | retval ! = 0 ) {
2022-02-20 01:58:38 +01:00
ERR_PRINT ( " move_to_trash: Could not move the resource \" " + path + " \" to the trash can \" " + trash_path + " /files \" " ) ;
2022-03-23 10:08:58 +01:00
Ref < DirAccess > dir_access = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
2022-02-20 01:58:38 +01:00
err = dir_access - > rename ( renamed_path , path ) ;
ERR_FAIL_COND_V_MSG ( err ! = OK , err , " Could not rename \" " + renamed_path + " \" back to its original name: \" " + path + " \" " ) ;
2020-11-30 21:19:52 +01:00
return FAILED ;
}
2020-03-03 14:36:29 +01:00
}
return OK ;
}
OS_LinuxBSD : : OS_LinuxBSD ( ) {
2020-04-02 01:20:12 +02:00
main_loop = nullptr ;
2020-03-03 14:36:29 +01:00
# ifdef PULSEAUDIO_ENABLED
AudioDriverManager : : add_driver ( & driver_pulseaudio ) ;
# endif
# ifdef ALSA_ENABLED
AudioDriverManager : : add_driver ( & driver_alsa ) ;
# endif
# ifdef X11_ENABLED
DisplayServerX11 : : register_x11_driver ( ) ;
# endif
2022-07-27 20:07:21 +02:00
# ifdef FONTCONFIG_ENABLED
# ifdef DEBUG_ENABLED
int dylibloader_verbose = 1 ;
# else
int dylibloader_verbose = 0 ;
# endif
font_config_initialized = ( initialize_fontconfig ( dylibloader_verbose ) = = 0 ) ;
# endif // FONTCONFIG_ENABLED
2020-03-03 14:36:29 +01:00
}