Repository: evan-liu/karabiner.ts
Pull request #101 (2023-09-04 21:09:45)

Extend ifApp condition to support both bundle_identifiers and file_paths
The `frontmost_application_if` condition in Karabiner supports using _both_ `bundle_identifier` and `file_path` rules simultaneously, evaluating as a match if _either_ is valid.

This is useful for things like mpv, which have a bundle id present _only_ if they're launched from the GUI shortcut, otherwise just expose a filename.



Patch:
diff --git a/src/config/condition.ts b/src/config/condition.ts
index dafb51c..ddd1515 100644
--- a/src/config/condition.ts
+++ b/src/config/condition.ts
@@ -20,7 +20,8 @@ export function ifApp(
   description?: string,
 ): ConditionBuilder
 export function ifApp(app: {
-  file_paths: Array<string | RegExp>
+  file_paths?: Array<string | RegExp>,
+  bundle_identifiers?: Array<string | RegExp>,
   description?: string
 }): ConditionBuilder
 export function ifApp(
@@ -28,7 +29,7 @@ export function ifApp(
     | string
     | RegExp
     | Array<string | RegExp>
-    | { file_paths: Array<string | RegExp> },
+    | { file_paths?: Array<string | RegExp>, bundle_identifiers?: Array<string | RegExp> },
   description?: string,
 ): ConditionBuilder {
   let bundle_identifiers: string[]
@@ -40,7 +41,9 @@ export function ifApp(
     return new ConditionBuilder({
       type: 'frontmost_application_if',
       description,
-      file_paths: app.file_paths.map(formatRegExp),
+      file_paths: app.file_paths?.map(formatRegExp),
+      bundle_identifiers: app.bundle_identifiers?.map(formatRegExp),
+
     })
   }
   return new ConditionBuilder({
@@ -109,7 +112,7 @@ const unlessTypes = flipUnlessTypes({
 })
 
 export class ConditionBuilder {
-  constructor(private readonly condition: Condition) {}
+  constructor(private readonly condition: Condition) { }
 
   /** Switch type {condition}_if to {condition}_unless, and vice versa */
   unless(): ConditionBuilder {
diff --git a/src/karabiner/karabiner-config.ts b/src/karabiner/karabiner-config.ts
index c6184ee..0a1b62d 100644
--- a/src/karabiner/karabiner-config.ts
+++ b/src/karabiner/karabiner-config.ts
@@ -37,11 +37,11 @@ export type FromModifiers = {
 export type FromEvent = (
   | FromKeyType
   | {
-      /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/from/simultaneous/ */
-      simultaneous: FromKeyType[]
-      /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/from/simultaneous-options/ */
-      simultaneous_options?: SimultaneousOptions
-    }
+    /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/from/simultaneous/ */
+    simultaneous: FromKeyType[]
+    /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/from/simultaneous-options/ */
+    simultaneous_options?: SimultaneousOptions
+  }
 ) & {
   /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/from/modifiers/ */
   modifiers?: FromModifiers
@@ -190,46 +190,48 @@ export type InputSource = {
 }
 
 export type Condition =
-  | ({
-      /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/conditions/frontmost-application/ */
-      type: 'frontmost_application_if' | 'frontmost_application_unless'
-      description?: string
-    } & ({ bundle_identifiers: string[] } | { file_paths: string[] }))
   | {
-      /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/conditions/device/ */
-      type:
-        | 'device_if'
-        | 'device_unless'
-        | 'device_exists_if'
-        | 'device_exists_unless'
-      identifiers: DeviceIdentifier[]
-      description?: string
-    }
+    /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/conditions/frontmost-application/ */
+    type: 'frontmost_application_if' | 'frontmost_application_unless'
+    description?: string,
+    bundle_identifiers?: string[],
+    file_paths?: string[]
+  }
   | {
-      /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/conditions/keyboard-type/ */
-      type: 'keyboard_type_if' | 'keyboard_type_unless'
-      keyboard_types: KeyboardType[]
-      description?: string
-    }
+    /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/conditions/device/ */
+    type:
+    | 'device_if'
+    | 'device_unless'
+    | 'device_exists_if'
+    | 'device_exists_unless'
+    identifiers: DeviceIdentifier[]
+    description?: string
+  }
   | {
-      /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/conditions/input-source/ */
-      type: 'input_source_if' | 'input_source_unless'
-      input_sources: InputSource[]
-      description?: string
-    }
+    /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/conditions/keyboard-type/ */
+    type: 'keyboard_type_if' | 'keyboard_type_unless'
+    keyboard_types: KeyboardType[]
+    description?: string
+  }
   | {
-      /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/conditions/variable/ */
-      type: 'variable_if' | 'variable_unless'
-      name: string
-      value: number | boolean | string
-      description?: string
-    }
+    /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/conditions/input-source/ */
+    type: 'input_source_if' | 'input_source_unless'
+    input_sources: InputSource[]
+    description?: string
+  }
+  | {
+    /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/conditions/variable/ */
+    type: 'variable_if' | 'variable_unless'
+    name: string
+    value: number | boolean | string
+    description?: string
+  }
   | {
-      /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/conditions/event-changed/ */
-      type: 'event_changed_if' | 'event_changed_unless'
-      value: boolean
-      description?: string
-    }
+    /** @see https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/conditions/event-changed/ */
+    type: 'event_changed_if' | 'event_changed_unless'
+    value: boolean
+    description?: string
+  }
 
 export type FrontmostApplicationCondition = Extract<
   Condition,
@@ -239,10 +241,10 @@ export type KeyboardTypeCondition = Extract<
   Condition,
   {
     type:
-      | 'device_if'
-      | 'device_unless'
-      | 'device_exists_if'
-      | 'device_exists_unless'
+    | 'device_if'
+    | 'device_unless'
+    | 'device_exists_if'
+    | 'device_exists_unless'
   }
 >
 export type InputSourceCondition = Extract<


Test patch:
diff --git a/src/config/condition.test.ts b/src/config/condition.test.ts
index 56942a7..bea6ae3 100644
--- a/src/config/condition.test.ts
+++ b/src/config/condition.test.ts
@@ -38,10 +38,19 @@ test('ifApp()', () => {
     bundle_identifiers: ['a', 'b'],
   })
 
+  expect(ifApp({bundle_identifiers: ["a", /b/]}).build()).toEqual({
+    type: 'frontmost_application_if',
+    bundle_identifiers: ["a", "b"],
+  })
   expect(ifApp({ file_paths: ['a', /b/] }).build()).toEqual({
     type: 'frontmost_application_if',
     file_paths: ['a', 'b'],
   })
+  expect(ifApp({bundle_identifiers: ["a", /b/], file_paths: ['c', /d/]}).build()).toEqual({
+    type: 'frontmost_application_if',
+    bundle_identifiers: ["a", "b"],
+    file_paths: ["c", "d"]
+  })
 })
 
 test('ifDevice()', () => {